微信支付爬坑记录(代码篇)

时间:2019-01-23
本文章向大家介绍微信支付爬坑记录(代码篇),主要包括微信支付爬坑记录(代码篇)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

近日写了些微信支付的接口怕日后忘了,写下来。方便以后回顾。
首先说明我用的是spring boot 的框架来+wxpay-sdk 来实现的。
微信支付分为很多种方式我这次实现的多以web为主所以只实现了 JSAPI支付,Native支付(pc上的扫码支付),H5支付。
首先我们要了加入微信支付的sdk的ar包。

maven:

	<dependency>
			<groupId>com.github.wxpay</groupId>
			<artifactId>wxpay-sdk</artifactId>
			<version>0.0.3</version>
		</dependency>

加入jar包后我去在我们的配置文件中加入我们的配置信息。

pay:
  wxpay:
     appID: 公众账号ID
     mchID: 商户号
     key: api密钥
     sandboxKey: 沙箱环境密钥
     certPath: API证书绝对路径
     notifyUrl:退款异步通知地址
     useSandbox: 是否看起沙箱测试

这里可以根据不同的项目进行配置。

之后我们去写获取配置文件。


@Data
@Slf4j
  /** 获取配置文件的内容 */
@ConfigurationProperties(prefix = "pay.wxpay")
public class MyWXPayConfig  implements WXPayConfig{
    /** 公众账号ID */
    private String appID;

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

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

    /** API 沙箱环境密钥 */
    private String sandboxKey;

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

    /** 退款异步通知地址 */
    private String notifyUrl;

    private Boolean useSandbox;

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

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


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

    @Override
    public String getKey(){
        if (useSandbox) {
            return sandboxKey;
        }

        return key;
    }

}

配置写完了我们要将wxpay-sdk 里面的使用类放入配置文件里。

@Configuration
@EnableConfigurationProperties(MyWXPayConfig.class)
public class WXPayConfiguration {

    @Autowired
    private MyWXPayConfig wxPayConfig;

    /**
     * useSandbox 沙盒环境
     *
     * @return
     */
    @Bean
    public WXPay wxPay() {
        return new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());
    }

    @Bean
    public WXPayClient wxPayClient() {
        return new WXPayClient(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());
    }

}

写到这里微信的基本配置差不多写完了。
为什么要用微信的sdk那。因为这里面微信内置了访问支付和退款等接口。对于微信的签名在这里不用自己写方法去做签名,基础参数如AppId,mchID,key 这些在配置文件中添加的不需要每次都拿出来。直接添加剩下的参数即可。省略了一些操作。在签名上sdk,会根据你的参数自行帮你签名。这样即减少了代码量。也解决了签名的错误问题。(ps:但也因为如此我前期做的时候没有看签名规则,吃了一次大亏。)

回归正题:
接下来我们开始写服务类。

@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private WXPay wxPay;

    @Autowired
    private WXPayClient wxPayClient;
    @Autowired
    private MyWXPayConfig myWXPayConfig;

    /**
     * 扫码支付
     * @param request 内置对象
     * @param outTradeNo 订单号
     * @param productId 商品id
     * @param body  商品说明
     * @param totalFee 标价金额
     * @param notifyUrl 通知地址
     * @return
     * @throws Exception
     */
    @Override
    public Map<String, String> wxPcSmPay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl) throws Exception {
        String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
        String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
        Map<String, String> map = WxPayEntityUtil.getPcSmPay(outTradeNo, productId, body, totalFee, urlH, request);
        System.out.println(map.get("spbill_create_ip"));
        Map<String, String> responseMap = wxPay.unifiedOrder(map);
        String returnCode = responseMap.get("return_code");
        String resultCode = responseMap.get("result_code");
        if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
            /*
               为了方便使用不泄露太多其他的信息。我精简了返回参数
             */
            Map<String, String> rmap = new HashMap<>();
            String codeUrl = responseMap.get("code_url");
            rmap.put("out_trade_no", outTradeNo);
            rmap.put("code_url", codeUrl);
            rmap.put("return_code", responseMap.get("return_code"));
            return rmap;
        }
        return responseMap;
    }
    /**
     * h5 支付
     * @param request 内置对象
     * @param outTradeNo 订单号
     * @param productId 商品id
     * @param body  商品说明
     * @param totalFee 标价金额
     * @param notifyUrl 通知地址
     * @return
     * @throws Exception
     */
    @Override
    public Map<String, String> wxH5Pay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl) throws Exception {
        String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
        String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
        Map<String, String> map = WxPayEntityUtil.getH5Pay(outTradeNo, productId, body, totalFee, urlH, request);
        System.out.println(map.get("spbill_create_ip"));
        Map<String, String> responseMap = wxPay.unifiedOrder(map);
        String returnCode = responseMap.get("return_code");
        String resultCode = responseMap.get("result_code");
        if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
            Map<String, String> rmap = new HashMap<>();
            String mweb_url = responseMap.get("mweb_url");
            rmap.put("out_trade_no", outTradeNo);
            rmap.put("mweb_url", mweb_url);
            rmap.put("return_code", responseMap.get("return_code"));
            return rmap;

        }
        return responseMap;
    }

    /**
     * JSPAIpay 支付
     * @param request 内置对象
     * @param outTradeNo 订单号
     * @param productId 商品id
     * @param body  商品说明
     * @param totalFee 标价金额
     * @param notifyUrl 通知地址
     * @param openId 用户唯一标识
     * @return
     * @throws Exception
     */
    @Override
    public Map<String, String> JSPAIpay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl, String openId) throws Exception {
        String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
        String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
        Map<String, String> map = WxPayEntityUtil.getJsApiPay(outTradeNo, productId, body, totalFee, urlH, openId, request);
        System.out.println(map.get("spbill_create_ip"));
        Map<String, String> responseMap = wxPay.unifiedOrder(map);
        String returnCode = responseMap.get("return_code");
        String resultCode = responseMap.get("result_code");
        Map<String, String> rmap = new HashMap<>();
        if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
            long timeStamp = new Date().getTime()/1000;
            Map<String, String> signMap =new HashMap<>();
            signMap.put("appId",responseMap.get("appid"));
            signMap.put("timeStamp",timeStamp+"");
            signMap.put("nonceStr",responseMap.get("nonce_str"));
            signMap.put("package","prepay_id="+responseMap.get("prepay_id"));
            signMap.put("signType","MD5");
            signMap.put("paySign", WXPayUtil.generateSignature(signMap,myWXPayConfig.getKey()));
            signMap.put("return_code","SUCCESS");

            return signMap;

        }
        return responseMap;
    }

    /**
     * 查询订单是否完成
     * @param orderId
     * @return
     * @throws SocketException
     */
    @Override
    public Map<String, String> queryPay(String orderId) throws SocketException {
        Map<String, String> resultMap = new HashMap<String, String>();
        for (int i = 0; i < 300; i++) {
            //"*************************调用支付查询 start*************************//
            Map<String, String> key = new HashMap<>();
            key.put("appid", myWXPayConfig.getAppID());
            key.put("mch_id", myWXPayConfig.getMchID());
            key.put("out_trade_no", orderId);
            key.put("sign", myWXPayConfig.getMchID());
            try {
                resultMap = wxPay.orderQuery(key);
                if ("SUCCESS".equals(resultMap.get("trade_state"))) {
                    return resultMap;
                }
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return resultMap;

    }

    /**
     * 回调地址 定点完成后会被请求
     * @param request
     * @param response
     * @throws Exception
     */

    @Override
    public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String, String> reqData = wxPayClient.getNotifyParameter(request);

        String returnCode = reqData.get("return_code");
        String resultCode = reqData.get("result_code");
        if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
            boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
            if (signatureValid) {
                // TODO 业务处理

                Map<String, String> responseMap = new HashMap<>(2);
                responseMap.put("return_code", "SUCCESS");
                responseMap.put("return_msg", "OK");
                String responseXml = WXPayUtil.mapToXml(responseMap);

                response.setContentType("text/xml");
                response.getWriter().write(responseXml);
                response.flushBuffer();
            }
        }
    }

   

}

ps: 工具类,WxPayEntityUtil是我做的一个方便将参数封装成 map的工具类。

我的理解wxpay sdk :
微信的支付接口说到底他就是一个访问接口。为了安全系数访问的参数必须是xml形式的参数,而且每次访问都系要sign 签名。而且大部分呢的接口都要我们的基础参数(appId,mchID,key)。

我们在最初获取配置文件的时候 继承了一个接口 WXPayConfig 。在wxpay 生成bean中的将这些配置(也就是基础的数据)放入到了bean中可以直接调用。之后在方法中接收到你的map参数。将固定的参数放入map中在生成sign签名放入mxl中向接口发送请求。

入坑点:
交易类型trade_type
JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付

虽然支付的接口是统一的但不同的支付类型需要不同的参数。

测试方便,有一些接口需要在线上测试。这点spring Boot开发真的很不爽,每次都要打包上传重启。如果是tomcat 运行项目或许可以 替换class 重启就好了。

当然还有微信支付平台上面的配置也很饶人。(ps:第一次去配置很懵圈。)

最后接口文档还是仔仔细细的看啊:
注:微信接口:https://pay.weixin.qq.com/wiki/doc/api/index.html