java 微信支付(v2)

此方法为微信V2支付,不支持V3
 

域名管理,实现主备域名自动切换:IWXPayDomain类

import com.github.wxpay.sdk.WXPayConfig;

/**
 * 域名管理,实现主备域名自动切换
 */
public abstract interface IWXPayDomain {
    /**
     * 上报域名网络状况
     * @param domain 域名。 比如:api.mch.weixin.qq.com
     * @param elapsedTimeMillis 耗时
     * @param ex 网络请求中出现的异常。
     *           null表示没有异常
     *           ConnectTimeoutException,表示建立网络连接异常
     *           UnknownHostException, 表示dns解析异常
     */
    abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);

    /**
     * 获取域名
     * @param config 配置
     * @return 域名
     */
    abstract DomainInfo getDomain(final WXPayConfig config);

    static class DomainInfo{
        public String domain;       //域名
        public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
        public DomainInfo(String domain, boolean primaryDomain) {
            this.domain = domain;
            this.primaryDomain = primaryDomain;
        }

        @Override
        public String toString() {
            return "DomainInfo{" +
                    "domain='" + domain + '\'' +
                    ", primaryDomain=" + primaryDomain +
                    '}';
        }
    }

}

微信支付的参数配置:MyWXPayConfig

import com.github.wxpay.sdk.WXPayConfig;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * 微信支付的参数配置
 *
 */
@Data
@Slf4j
@ConfigurationProperties(prefix = "pay.wxpay")
public class MyWXPayConfig implements WXPayConfig{

	/** appID */
	private String appID = "";

	/** 商户号 */
	private String mchID = "";

	/** API 密钥 */
	private String key = "";

	/** API证书绝对路径 */
	private String certPath;

	/** 异步通知地址 */
	private String notifyUrl = "http://192.168.10.100:8080/wxPay/notify";

	/** HTTP(S) 连接超时时间,单位毫秒 */
	private int httpConnectTimeoutMs = 8000;

	/** HTTP(S) 读数据超时时间,单位毫秒 */
	private int httpReadTimeoutMs = 10000;

	/**
	 * 企业付款接口
	 */
	public String transfersUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";

	/**
	 * 企业付款查询API
	 */
	private String transfersPayQuery = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";

	/**
	 * 企业付款接口
	 */
	public String transfersPayBank = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank";

	/**
	 * 获取商户证书内容
	 *
	 * @return 商户证书内容
	 */
	@Override
	public InputStream getCertStream()  {
		InputStream inputStream = null;
		try {
			File file = ResourceUtils.getFile("classpath:wxPay/apiclient_cert.pem");
			//File file = ResourceUtils.getFile("resource:wxPay/apiclient_cert.pem");
			inputStream = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			//log.error("cert file not found, path={}, exception is:{}", certPath, e);
			e.printStackTrace();
		}
		return inputStream;
	}


	@Override
	public String getKey(){
		return key;
	}

	@Override
	public String getAppID() {
		return appID;
	}

	@Override
	public int getHttpConnectTimeoutMs() {
		return httpConnectTimeoutMs;
	}

	@Override
	public int getHttpReadTimeoutMs() {
		return httpReadTimeoutMs;
	}

	@Override
	public String getMchID() {
		return mchID;
	}


	public boolean getUseSandbox() {
		return false;
	}


	public String getNotifyUrl() {
		return notifyUrl;
	}


	public String getCertPath() {
		return certPath;
	}


	public void setCertPath(String certPath) {
		this.certPath = certPath;
	}


	public void setAppID(String appID) {
		this.appID = appID;
	}


	public void setMchID(String mchID) {
		this.mchID = mchID;
	}


	public void setKey(String key) {
		this.key = key;
	}


	public void setNotifyUrl(String notifyUrl) {
		this.notifyUrl = notifyUrl;
	}


	public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) {
		this.httpConnectTimeoutMs = httpConnectTimeoutMs;
	}


	public void setHttpReadTimeoutMs(int httpReadTimeoutMs) {
		this.httpReadTimeoutMs = httpReadTimeoutMs;
	}

	public String getTransfersUrl() {
		return transfersUrl;
	}

	public void setTransfersUrl(String transfersUrl) {
		this.transfersUrl = transfersUrl;
	}

	public String getTransfersPayQuery() {
		return transfersPayQuery;
	}

	public void setTransfersPayQuery(String transfersPayQuery) {
		this.transfersPayQuery = transfersPayQuery;
	}

	public String getPcAppId() {
		return pcAppId;
	}

	public void setPcAppId(String pcAppId) {
		this.pcAppId = pcAppId;
	}

	public String getTransfersPayBank() {
		return transfersPayBank;
	}

	public void setTransfersPayBank(String transfersPayBank) {
		this.transfersPayBank = transfersPayBank;
	}
}

对WXPay的简单封装,处理支付密切相关的逻辑:WXPayClient

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;

import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * WXPayClient
 * <p>
 * 对WXPay的简单封装,处理支付密切相关的逻辑.
 *
 * @version 1.0
 */
@Slf4j
public class WXPayClient extends WXPay {

    //** 密钥算法 */
    private static final String ALGORITHM = "AES";
    /** 加解密算法/工作模式/填充方式 */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
    /** 用户支付中,需要输入密码 */
    private static final String ERR_CODE_USERPAYING = "USERPAYING";
    private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE";
    /** 交易状态: 未支付 */
    private static final String TRADE_STATE_NOTPAY = "NOTPAY";

    /** 用户输入密码,尝试30秒内去查询支付结果 */
    private static Integer remainingTimeMs = 10000;

    private WXPayConfig config;

    public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) {
        super(config, signType, useSandbox);
        this.config = config;
    }

    /**
     *
     * 刷卡支付
     *
     * 对WXPay#microPay(Map)增加了当支付结果为USERPAYING时去轮询查询支付结果的逻辑处理
     *
     * 注意:该方法没有处理return_code=FAIL的情况,暂时不考虑网络问题,这种情况直接返回错误
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> microPayWithPOS(Map<String, String> reqData) throws Exception {
        // 开始时间(毫秒)
        long startTimestampMs = System.currentTimeMillis();

        Map<String, String> responseMapForPay = super.microPay(reqData);
        //log.info(responseMapForPay.toString());

        // // 先判断 协议字段返回(return_code),再判断 业务返回,最后判断 交易状态(trade_state)
        // 通信标识,非交易标识
        String returnCode = responseMapForPay.get("return_code");
        if (WXPayConstants.SUCCESS.equals(returnCode)) {
            String errCode = responseMapForPay.get("err_code");
            // 余额不足,信用卡失效
            if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) {
                Map<String, String> orderQueryMap = null;
                Map<String, String> requestData = new HashMap<>();
                requestData.put("out_trade_no", reqData.get("out_trade_no"));

                // 用户支付中,需要输入密码或系统错误则去重新查询订单API err_code, result_code, err_code_des
                // 每次循环时的当前系统时间 - 开始时记录的时间 > 设定的30秒时间就退出
                while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) {
                    // 商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。
                    orderQueryMap = super.orderQuery(requestData);
                    String returnCodeForQuery = orderQueryMap.get("return_code");
                    if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) {
                        // 通讯成功
                        String tradeState = orderQueryMap.get("trade_state");
                        if (WXPayConstants.SUCCESS.equals(tradeState)) {
                            // 如果成功了直接将查询结果返回
                            return orderQueryMap;
                        }
                        // 如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果
                        Thread.sleep(1000);
                    }
                }

                // 如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。
                String tradeState = orderQueryMap.get("trade_state");
                if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) {
                    Map<String, String> reverseMap = this.reverse(requestData);
                    String returnCodeForReverse = reverseMap.get("return_code");
                    String resultCode = reverseMap.get("result_code");
                    if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) {
                        // 如果撤销成功,需要告诉客户端已经撤销订单了
                        responseMapForPay.put("err_code_des", "用户取消支付或尚未支付,后台已经撤销该订单,请重新支付!");
                    }
                }
            }
        }

        return responseMapForPay;
    }

    /**
     * 从request的inputStream中获取参数
     * @param request
     * @return
     * @throws Exception
     */
    public Map<String, String> getNotifyParameter(HttpServletRequest request) throws Exception {
        InputStream inputStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, length);
        }
        outSteam.close();
        inputStream.close();

        // 获取微信调用我们notify_url的返回信息
        String resultXml = new String(outSteam.toByteArray(), "utf-8");
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml);

        return notifyMap;
    }

    /**
     * 解密退款通知
     *
     * <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_16&index=11>退款结果通知文档</a>
     * @return
     * @throws Exception
     */
    @SuppressWarnings("restriction")
    public Map<String, String> decodeRefundNotify(HttpServletRequest request) throws Exception {
        // 从request的流中获取参数
        Map<String, String> notifyMap = this.getNotifyParameter(request);
        //log.info(notifyMap.toString());

        String reqInfo = notifyMap.get("req_info");
        //(1)对加密串A做base64解码,得到加密串B
        byte[] bytes = new BASE64Decoder().decodeBuffer(reqInfo);

        //(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);

        //(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
        // java.security.InvalidKeyException: Illegal key size or default parameters
        // https://www.cnblogs.com/yaks/p/5608358.html
        String responseXml = new String(cipher.doFinal(bytes),"UTF-8");
        Map<String, String> responseMap = WXPayUtil.xmlToMap(responseXml);
        return responseMap;
    }

    /**
     * 获取沙箱环境
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凱凱啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值