第十一节 微信支付的服务端

亮子 2021-08-23 20:36:38 17666 0 0 0

1、需要准备的数据

  • 商户号
  • 商户API证书的证书序列号
  • 商户API私钥,如何加载商户API私钥请看常见问题。
  • 微信支付平台证书。你也可以使用后面章节提到的“自动更新证书功能”,而不需要关心平台证书的来龙去脉。
# native模式的统一下单接口
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml

2、添加依赖

        <!-- 微信支付 -->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.2.2</version>
        </dependency>

3、获取订单预付ID

package com.shenmazong.shenmacodeserver.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.*;
import java.security.PrivateKey;

/**
 * @author 军哥
 * @version 1.0
 * @description: TODO
 * @date 2021/8/23 20:42
 */

@Configuration
public class WeiXinPayConfig {

    /**
     * 商户号
     */
    @Value("${weixin.merchantId}")
    private String merchantId;

    /**
     * 商户API私钥
     */
    @Value("${weixin.merchantSerialNumber}")
    private String merchantSerialNumber;

    /**
     * 微信支付平台证书文件存储路径
     */
    @Value("${weixin.privateKeyPath}")
    private String privateKeyPath;

    /**
     * apiV3 的密码
     */
    @Value("${weixin.apiV3Key}")
    private String apiV3Key;

    /**
     * APP ID
     */
    @Value("${weixin.appId}")
    private String appId;

    /**
     * 回调URL
     */
    @Value("${weixin.notifyUrl}")
    private String notifyUrl;

    @Bean
    public WeiXinPayConfig weiXinPayUtil() {
        return new WeiXinPayConfig();
    }


    public String prepayIdByOrderInfo(String orderId, String product, Long amount, String openId) throws IOException {

        //--1 读取证书
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new FileInputStream(privateKeyPath));

        //--2 构建请求
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
                apiV3Key.getBytes("utf-8"));

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));

        //--3 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        HttpClient httpClient = builder.build();

        //--4 调用统一下单接口
        // https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",merchantId)
                .put("appid", appId)
                .put("description", product)
                .put("notify_url", notifyUrl)
                .put("out_trade_no", orderId);
        rootNode.putObject("amount")
                .put("total", amount);
        rootNode.putObject("payer")
                .put("openid", openId);

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bodyAsString);

        return bodyAsString;

    }
}

4、【服务端】接收支付结果通知

步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意:

  • 支付结果通知是以 POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
  • 对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)

参考文档:

https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml