springboot integrates IJPay to realize WeChat payment-V3---WeChat applet

foreword

WeChat payment is suitable for many occasions, such as small programs, web page payment, but WeChat payment is a little troublesome compared to other payment methods, we use the IJpay framework for integration

1. What is IJpay?

JPay makes payment at your fingertips, and encapsulates the commonly used payment methods of WeChat payment, Alipay payment, UnionPay payment and various commonly used interfaces. It does not rely on any third-party mvc framework, it is only used as a tool to complete the development of the payment module easily and quickly, and can be easily embedded into any system.

2. Use steps

1. Prepare the necessary information for the Mini Program

1.1 To associate a merchant account on the Mini Program

1.2 Configure relevant information in the application.yml file

This is the certificate downloaded from the WeChat platform

1.3 Import IJpay dependency

    <dependency>
            <groupId>com.github.javen205</groupId>
            <artifactId>IJPay-WxPay</artifactId>
            <version>2.9.6</version>
        </dependency>

2. Concrete operation

2.1 New controller WxPayApiContoller

2.2 Controller code:

package cn.cnvp.web.api.wechart;

import cn.cnvp.web.token.message.JsonResult;
import cn.cnvp.web.utils.WxUtils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.enums.v3.OtherApiEnum;
import com.ijpay.wxpay.model.v3.Amount;
import com.ijpay.wxpay.model.v3.Payer;
import com.ijpay.wxpay.model.v3.UnifiedOrderModel;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: Scoot
 * @Date: 2023/3/16 15:52
 * @Description: 微信支付控制器
 */
@Slf4j
@Api(tags = "微信支付控制器")
@RestController
@RequestMapping("/api/wx/pay/v1")
@Scope("prototype")
public class WxPayApiController {


    /**微信小程序appid**/
    @Value("${wechat.ma.appId}")
    String appid;

    /**微信小程序secretId**/
    @Value("${wechat.ma.secret}")
    String secret;

    /**商户号**/
    @Value("${wechat.ma.mchid}")
    String mchid;

    /**商户密钥**/
    @Value("${wechat.ma.mchKey}")
    String mchKey;

    /**回调地址**/
    @Value("${wechat.ma.notifyUrl}")
    String notifyUrl;

    /**证书地址**/
    @Value("${wechat.ma.certPath}")
    String certPath;
    /**证书密钥地址**/
    @Value("${wechat.ma.certKeyPath}")
    String certKeyPath;
    /**微信平台证书**/
    @Value("${wechat.ma.platFormPath}")
    String platFormPath;



    /**
     * 微信支付
     * @param openId  用户openId
     * @return
     */
    @RequestMapping("/jsApiPay")
    @ResponseBody
    public JsonResult jsApiPay(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
        try {
            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
            UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                    // APPID
                    .setAppid(appid)
                    // 商户号
                    .setMchid(mchid)
                    .setDescription("IJPay 让支付触手可及")
                    .setOut_trade_no(PayKit.generateStr())
                    .setTime_expire(timeExpire)
                    .setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
                    .setNotify_url(notifyUrl)
                    .setAmount(new Amount().setTotal(1))
                    .setPayer(new Payer().setOpenid(openId));

            log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    BasePayApiEnum.JS_API_PAY.toString(),
                    mchid,
                    getSerialNumber(),
                    null,
                    certKeyPath,
                    JSONUtil.toJsonStr(unifiedOrderModel)
            );
            log.info("统一下单响应 {}", response);
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, platFormPath);
            log.info("verifySignature: {}", verifySignature);
            if (response.getStatus() == HttpStatus.HTTP_OK && verifySignature) {
                String body = response.getBody();
                JSONObject jsonObject = JSONUtil.parseObj(body);
                String prepayId = jsonObject.getStr("prepay_id");
                Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, certKeyPath);
                log.info("唤起支付参数:{}", map);
                return JsonResult.success("获取成功",JSONUtil.toJsonStr(map));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return JsonResult.error("唤起失败");
    }

    /**
     * 微信支付回调
     * @param request
     * @param response
     */
    @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
    public void payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>(12);
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");

            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpKit.readData(request);
            log.info("支付通知密文 {}", result);

            // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                    mchKey, platFormPath);

            log.info("支付通知明文 {}", plainText);

            if (StrUtil.isNotEmpty(plainText)) {
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private String getSerialNumber() {
        // 获取证书序列号
        X509Certificate certificate = PayKit.getCertificate(certPath);
        if (null != certificate) {
            String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            // 提前两天检查证书是否有效
            boolean isValid = PayKit.checkCertificateIsValid(certificate, mchid, -2);
            log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
            System.out.println("serialNo:" + serialNo);
            return serialNo;
        }
        return null;
    }



    @RequestMapping("/get")
    @ResponseBody
    public String v3Get() {
        // 获取平台证书列表
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    OtherApiEnum.GET_CERTIFICATES.toString(),
                    mchid,
                    getSerialNumber(),
                    null,
                    certKeyPath,
                    ""
            );
            String serialNumber = response.getHeader("Wechatpay-Serial");
            String body = response.getBody();
            int status = response.getStatus();
            log.info("serialNumber: {}", serialNumber);
            log.info("status: {}", status);
            log.info("body: {}", body);
            int isOk = 200;
            if (status == isOk) {
                JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONArray dataArray = jsonObject.getJSONArray("data");
                // 默认认为只有一个平台证书
                JSONObject encryptObject = dataArray.getJSONObject(0);
                JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                String associatedData = encryptCertificate.getStr("associated_data");
                String cipherText = encryptCertificate.getStr("ciphertext");
                String nonce = encryptCertificate.getStr("nonce");
                String serialNo = encryptObject.getStr("serial_no");
                final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, platFormPath);
                log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
            }
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, platFormPath);
            System.out.println("verifySignature:" + verifySignature);
            return body;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }



    /**
     * 保存平台证书
     * @param associatedData    associated_data
     * @param nonce             nonce
     * @param cipherText        cipherText
     * @param certPath          证书保存路径
     * @return
     */
    private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
        try {
            AesUtil aesUtil = new AesUtil(mchKey.getBytes(StandardCharsets.UTF_8));
            // 平台证书密文解密
            // encrypt_certificate 中的  associated_data nonce  ciphertext
            String publicKey = aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    cipherText
            );
            // 保存证书
            cn.hutool.core.io.file.FileWriter writer = new FileWriter(certPath);
            writer.write(publicKey);
            // 获取平台证书序列号
            X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
            return certificate.getSerialNumber().toString(16).toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

}

2.3 Download platform certificate platForm.pem

Call the above get method to download the certificate After the download is complete, the platForm.pem certificate will be generated in the path configured by yml

2.4 Start the test

The interface passes in openID (other methods can be changed later), and the following json string is returned to indicate success

2.5 Small programs evoke WeChat payment

2.5.1 uniapp code

<template>
    <view class="content">
        <button @click="pay">支付</button>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                title: 'Hello',
                sign :{}
            }
        },
        onLoad() {
        
        },
        methods: {
            pay(){
                uni.request({
                    // 获取微信支付签名地址
                    url:"http://192.168.0.231:8087/api/wx/pay/v1/jsApiPay?openId=oEmg75RAQX-7uRqGUkSy0tGcfzpI",
                     // 成功回调
                     success: (res) => {
                        let sign =JSON.parse(res.data.data);
                        // 仅作为示例,非真实参数信息。
                        uni.requestPayment({
                            provider: 'wxpay',
                            timeStamp: sign.timeStamp,
                            nonceStr: sign.nonceStr,
                            package: sign.package,
                            signType: sign.signType,
                            paySign: sign.paySign,
                            success: function (res) {
                                console.log('success:' + JSON.stringify(res));
                            },
                            fail: function (err) {
                                console.log('fail:' + JSON.stringify(err));
                            }
                        });
                    }
                })
                
            }
            
        }
    }
</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .logo {
        height: 200rpx;
        width: 200rpx;
        margin-top: 200rpx;
        margin-left: auto;
        margin-right: auto;
        margin-bottom: 50rpx;
    }

    .text-area {
        display: flex;
        justify-content: center;
    }

    .title {
        font-size: 36rpx;
        color: #8f8f94;
    }
</style>

2.6 Asynchronous callback

Asynchronous callback address configured in yml (requires external network access)

Summarize

The above is what I want to talk about today. This article only briefly introduces the use of springboot to integrate IJpay for WeChat payment. The lack of parameters for calling payment jspi: total_fee is mainly because 1. The amount is empty 2. The order number is repeated 3. The order number is empty

Guess you like

Origin blog.csdn.net/qq_37612049/article/details/129589510