springboot+微信小程序接入微信小程序支付(使用证书与JSAPI)

1、maven引入依赖


    com.github.wechatpay-apiv3
    wechatpay-apache-httpclient
    0.4.5

2、配置文件application.yml

#微信支付相关参数
wx-pay:
    #商户id(微信支付商户平台获取)
    mch-id: xxxxxxxxx
    #公众号appid(和商户id绑定过后,微信支付商户平台或者微信公众平台获取)
    appid: xxxxxxxxx

    #商户证书序列号(从微信支付商户平台-API安全获取)
    mch-serial-no: xxxxxxxxx
    #商户私钥(从微信支付商户平台-API安全获取,放在项目根目录与src同级)
    private-key-path: apiclient_key.pem
    #APIv3密钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
    api-v3-key: xxxxxxxxx

    #微信服务器地址
    domain: https://api.mch.weixin.qq.com
    #接收结果通知地址(自己项目的域名,发布到服务器上就是服务器的域名,必须域名不支持ip,本地调试需要使用内网穿透-推荐使用花生壳来做内网穿透,但会收取少量费用,也可使用ngrok,免费但每次启动域名会变化,需要修改此处地址)
    notify-domain: https://xxxxxxxxx

3、支付配置类WxPayConfig(GitHub - wechatpay-apiv3/wechatpay-apache-httpclient: 微信支付 APIv3 Apache HttpClient装饰器(decorator))

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

@Configuration
@Data
public class WxPayConfig {
    @Value("${wx-pay.mch-id}")
    private String mchId;

    @Value("${wx-pay.appid}")
    private String appid;

    @Value("${wx-pay.mch-serial-no}")
    private String mchSerialNo;

    @Value("${wx-pay.private-key-path}")
    private String privateKeyPath;

    @Value("${wx-pay.api-v3-key}")
    private String apiV3Key;

    @Value("${wx-pay.domain}")
    private String domain;

    @Value("${wx-pay.notify-domain}")
    private String notifyDomain;

    /**
     * 获取商户私钥
     * */
    private PrivateKey getPrivateKey(String filename){
        try {
            return  PemUtil.loadPrivateKey(
                    new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在",e);
        }
    }

   
    @Bean(name = "wxPayHttpClientOne")
    public CloseableHttpClient wxPayHttpClient(){

        //私钥
        PrivateKey mchPrivateKey= getPrivateKey(privateKeyPath);
        //微信证书校验器
        Verifier verifier=null;

        try {
            // 获取证书管理器实例
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                    new PrivateKeySigner(mchSerialNo, mchPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
            // ... 若有多个商户号,可继续调用putMerchant添加商户信息

            // 从证书管理器中获取verifier
            verifier = certificatesManager.getVerifier(mchId);
        } catch (Exception e) {
            new RuntimeException("微信证书校验器配置失败",e);
        }
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, mchPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

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

4、返回前台的实体类(微信支付文档有要求JSAPI调起支付_JSAPI支付|微信支付商户文档中心)

@Data
public class PaymentDto {

    private String appId;
    private String timeStamp;
    private String nonceStr;
    private String prepayId;
    private String signType;
    private String paySign;

}

5、后台获取小程序发起支付需要的参数

@TableName("order_info")
@Data
public class OrderInfo {
    //根据自己项目
    private String outTradeNo;
}
@PostMapping({"/createPay"})
public JsonResult createPay(@RequestBody OrderInfo entity) {
    PayMentDto response = orderInfoService.createPay(entity);
    return JsonResult.OK().data(response);
}

    @SaIgnore
    @PostMapping("/callback")
    public JsonResult paySignal(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try{
            Gson gson = new Gson();
            Map map = gson.fromJson(request.getReader(), Map.class);
            log.info(map.toString());
            log.info("支付id:{}", map.get("id"));
        }catch (Exception e){

        }

        return JsonResult.OK().data("444");
    }
@Service
@Slf4j
public class OrderInfoService {

    @Value("${wx-pay.mch-id}")
    private String mchId;

    @Value("${wx-pay.appid}")
    private String appid;

    @Value("${wx-pay.mch-serial-no}")
    private String mchSerialNo;

    @Value("${wx-pay.private-key-path}")
    private String privateKeyPath;

    @Value("${wx-pay.api-v3-key}")
    private String apiV3Key;

    @Value("${wx-pay.domain}")
    private String domain;

    @Value("${wx-pay.notify-domain}")
    private String notifyDomain;

    @Resource(name = "wxPayHttpClientOne")
    private CloseableHttpClient wxPayHttpClient;

    //
    public PaymentDto createPay(OrderInfo entity) throws IOException {
        //请求构造
        HttpPost httpPost=new HttpPost(domain+WxApiType.JSAPI_PAY.getType());
        //构造数据,根据官方文档需求构造
        HashMap reqData=new HashMap<>();
        reqData.put("appid",appid);
        reqData.put("mchid",mchId);
        reqData.put("description","订单详情");
        reqData.put("out_trade_no","商户系统生成的订单号");
        reqData.put("notify_url",notifyDomain+"/callback");
        HashMap amount=new HashMap<>();
        //单位是分
        amount.put("total",1);
        reqData.put("amount",amount);

        HashMap payer=new HashMap<>();
        payer.put("openid",xxxxxxxxx);
        reqData.put("payer",payer);

        String jsonReqData=new Gson().toJson(reqData);
        StringEntity entity=new StringEntity(jsonReqData,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        //请求头
        httpPost.setHeader("Accept","application/json");
        //完成签名并执行请求
        CloseableHttpResponse response= wxPayHttpClient.execute(httpPost);
        Map dataMap=null;
        PaymentDto paymentDto = new PaymentDto();
        try{
            int statusCode=response.getStatusLine()
                    .getStatusCode();

            //成功
            if(statusCode==200){
                String body= EntityUtils.toString(response.getEntity());
                dataMap=new Gson().fromJson(body,HashMap.class);
                String timeStamp = String.valueOf(DateUtil.current());
                String nonceStr = RandomUtil.randomString(RandomUtil.randomInt(1, 33));
                //拿到prepare_id存入数据库订单,把订单信息传回前端提示用户发起支付,这个data根据官方文档要求需要自己拼接,注意:\n是必须要的。
                String data = "wxkjdjjfjkhjhjhj\n"+ timeStamp+"\n"+nonceStr+"\nprepay_id="+dataMap.get("prepay_id")+"\n";
                RSA rsa = new RSA(PemUtil.loadPrivateKey(
                        Files.newInputStream(Paths.get("apiclient_key.pem"))), null);

                // 创建签名对象并指定算法
                Sign sign = new Sign(SignAlgorithm.SHA256withRSA, rsa.getPrivateKey(), null);

                // 对数据进行签名
                byte[] signedData = sign.sign(data.getBytes(StandardCharsets.UTF_8));
                String base64Signature = Base64.getEncoder().encodeToString(signedData);

                paymentDto.setAppId(appid);
                paymentDto.setTimeStamp(timeStamp);
                paymentDto.setNonceStr(nonceStr);
                paymentDto.setPrepayId(dataMap.get("prepay_id"));
                paymentDto.setSignType("RSA");
                paymentDto.setPaySign(base64Signature);
            }
            //失败
            else{
                if(statusCode!=204){
                    String body=EntityUtils.toString(response.getEntity());
                    log.error(body);
                }
            }
        }
        finally{
            response.close();
        }
        return paymentDto;
    }

}

6、前台发起支付






你可能感兴趣的:(spring,boot,微信小程序,后端)