2023年使用SDK实现小程序加Java后端完成V3接口支付

1.前期准备

1.1业务流程图

摘自微信官方业务流程图

1.2.概要流程

1、小程序,传递微信支付需要参数,比如订单号,使用者的OpenId,金额等等,调起统一支付接口进行预付下单
2、后端调用微信支付系统后生成6个必要参数返回给前端。
后台调用微信支付系统需要组装必要的参数:
{
	"amount": {
		"total": 1 //支付金额 单位为分
	},
	"mchid": "填写自己的商户号", //商户号
	"description": "支付描述",//支付描述
	"notify_url": "支付回调接收地址",//支付后回调接收地址
	"payer": {
		"openid": "当前使用者的openId" //当前用户的openID
	},
	"out_trade_no": "订单号,也就是业务订单号自己进行生成",//支付的订单号,支付后回调信息会将这个订单号带回来
	"appid": "小程序的AppId" //小程序的APPID
}
36个必要参数用于小程序能否成功唤起微信支付
如下:
订单号、金额、openid等去请求微信下单接口,微信返回预支付交易会话标识prepay_id
后端给appid、timestamp、nonceStr、prepayId签名,并将签名、timestamp、nonceStr、prepay_id返回给小程序
{
	"appId": "小程序的APPID",//小程序的APPID
	"timeStamp": "时间戳",//时间戳
	"nonceStr": "随机串,保证唯一",//随机串,保证唯一
	"package": "",
	"signType": "",//签名类型
	"paySign": "" //签名信息
}
4、小程序调用wx.requestPayment拉起微信支付
5、用户支付后,微信支付系统会回调信息,后端接收,做对应的业务逻辑处理

1.3.微信支付前必要准备

1、微信官方文档–小程序支付接口文档描述(最终是组成概要流程中的参数形式):
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
2、微信官方文档–商户号及微信V3证书下载
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
V3证书下载成功后文件(共3个)如下图:
apiclient_key.pem,apiclient_cert.pem,apiclient_cert.p12

证书两个pem文件,一个p12文件

1.4.所需要的所有参数文字说明

获取商户号
微信商户平台:https://pay.weixin.qq.com/ 步骤:申请成为商户 => 提交资料 => 签署协议 => 获取商户号

获取AppID
微信公众平台:https://mp.weixin.qq.com/ 步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号

申请商户证书
步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥

获取微信的证书
可以预先下载,也可以通过编程的方式获取。

获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 设置APIv3密钥

1.5.需要安装的Maven依赖包

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.9</version>
        </dependency>

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

因为IJPay-WxPay聚合支付每个版本他的SDK可能调用参数不一样,这里我调通的是2.9.8版本。
附上gitee地址:https://gitee.com/javen205/IJPay
也请大家给IJPay点点Star收藏一下

2.Java示例代码

2.1.Controller层

@RequiredArgsConstructor
@RestController
@RequestMapping("/payment")
public class WeChatPayController {
    private final WechatPayService wxPayNewService;

    /**
     * 微信统一下单接口
     *
     * @param iWxPayParamVO 业务需要的参数
     * @return
     */
    @PostMapping("/doUnifiedOrder")
    public Map<String, String> doUnifiedOrder() throws Exception {
        return wxPayNewService.doUnifiedOrder();
    }

    /**
     * 微信支付的回调接收接口
     *
     * @param request
     * @param response
     */
    @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        wxPayNewService.callBack(request, response);
    }

    /**
     * 生成v3证书
     */
    @RequestMapping("/createPlatformCert")
    @ResponseBody
    public String createPlatformCert() throws IOException {
        return wxPayNewService.createPlatformCert();
    }

    /**
     * 通过订单号查询支付情况
     *
     * @param outTradeNo 订单号
     * @return String
     */
    @RequestMapping("/query")
    @ResponseBody
    public String query(@RequestParam String outTradeNo) {
        return wxPayNewService.query(outTradeNo);
    }
}

2.2.Service层

public interface WechatPayService {
    /**
     * 微信统一下单接口
     *
     * @return Map
     * @throws Exception 异常
     */
    Map<String, String> doUnifiedOrder() throws Exception;

    /**
     * 获取v3的证书
     *
     * @return String类型
     * @throws IOException IO异常
     */
    String createPlatformCert() throws IOException;

    /**
     * 微信支付回调接口
     *
     * @param request  请求
     * @param response 详情
     */
    void callBack(HttpServletRequest request, HttpServletResponse response);

    /**
     * 查询账单
     *
     * @param outTradeNo
     * @return
     */
    String query(String outTradeNo);
}

2.3.ServiceImpl层

@RequiredArgsConstructor
@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {

    /**
     * 微信用户openId
     */
    private final String openId = "";
    /**
     * 这个是微信小程序的appid
     */
    private final String appId = "";
    /**
     * 商户号Id
     */
    private final String mchId = "";
    /**
     * 平台证书地址,平台证书需要手动下载或者使用v3接口进行下载
     */
    private final String platformCertPath = "";

    /**
     * 客户证书地址,也就是apiclient_cert.pem的路径
     */
    private final String apiClientCertPath = "";
    /**
     * 证书密钥地址,也就是apiclient_key.pem的路径
     */
    private final String apiClientKeyPath = "";
    /**
     * 回调地址,需要进行公网上,开发环境建议使用内网穿透到,/payment/payNotify的路径中去
     */
    private final String notifyUrl = "";


    /**
     * 微信统一下单接口
     *
     * @return Map
     * @throws Exception 异常
     */
    @Override
    public Map<String, String> doUnifiedOrder() throws Exception {
        //先写死1, 1就是1分钱,100=1元
        int price = 1;
        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                .setAppid(appId)
                .setMchid(mchId)
                .setDescription("支付说明")
                //这是随机数
                .setOut_trade_no(PayKit.generateStr())
                .setTime_expire(timeExpire)
                .setAttach("附加说明")
                //回调地址
                .setNotify_url(notifyUrl)
                .setAmount(new Amount().setTotal(price))
                .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,
                apiClientKeyPath,
                JSONUtil.toJsonStr(unifiedOrderModel)
        );
//        log.info("统一下单响应 {}", response);
        Map<String, String> map = new HashMap<>(16);
        //这个是证书文件,先写死,后续调整成读取证书文件的服务器存放地址,根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignatures = WxPayKit.verifySignature(response, platformCertPath);
        log.info("verifySignature: {}", verifySignatures);
        if (response.getStatus() == HttpServletResponse.SC_OK && verifySignatures) {
            // 根据证书序列号查询对应的证书来验证签名结果
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            // 私钥
            map = WxPayKit.jsApiCreateSign(appId, prepayId, apiClientKeyPath);
//            log.info("唤起支付参数:{}", map);
        }
        // todo 微信预支付的订单新入库,为了业务查询记录
        return map;
    }

    /**
     * 微信支付回调接口
     *
     * @param request  请求
     * @param response 详情
     */
    @Override
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        log.info("收到微信支付回调");
        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);
            // 根据证书序列号查询对应的证书来验证签名结果
            String platformCertPath = this.platformCertPath;
            //这个商户号对应的那个V3秘钥
            String mckKey = "";
            //需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, mckKey, platformCertPath);
            log.info("支付通知明文 {}", plainText);
            //这个就是具体的业务情况
            savePayPlainText(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();
        }
    }

    /**
     * 查询账单
     *
     * @param outTradeNo
     * @return
     */
    @Override
    public String query(String outTradeNo) {
        try {
            Map<String, String> params = new HashMap<>(16);
            params.put("mchid", mchId);

            log.info("统一下单参数 {}", JSONUtil.toJsonStr(params));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    String.format(BasePayApiEnum.ORDER_QUERY_BY_OUT_TRADE_NO.toString(), outTradeNo),
                    wxPayConfig.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayConfig.getApiClientKeyPath(),
                    params
            );
            log.info("查询响应 {}", response);
            if (response.getStatus() == HttpServletResponse.SC_OK) {
                // 根据证书序列号查询对应的证书来验证签名结果
                boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
                log.info("verifySignature: {}", verifySignature);
                return response.getBody();
            }
            return JSONUtil.toJsonStr(response);
        } catch (Exception e) {
            log.error("系统异常", e);
            return e.getMessage();
        }
    }

    /**
     * 获取v3的证书
     *
     * @return String类型
     */
    @Override
    public String createPlatformCert() {
        //这个商户号对应的那个V3秘钥
        String mckKey = "";
        // 获取平台证书列表
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
                    mchId,
                    getSerialNumber(),
                    null,
                    apiClientKeyPath,
                    "",
                    AuthTypeEnum.RSA.getCode()
            );
            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);
            if (status == HttpServletResponse.SC_OK) {
                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, mckKey, nonce, cipherText, platformCertPath);
                log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
            }
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, platformCertPath);
            if (verifySignature) {
                return body;
            } else {
                return "平台证书不正确";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "不能获取平台证书";
        }
    }

    private String savePlatformCert(String associatedData, String apiKey3, String nonce, String cipherText, String certPath) {
        try {
            AesUtil aesUtil = new AesUtil(apiKey3.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
            );
//            log.info("获取证书key:{},保存路径platformCert:{}", publicKey, certPath);
            log.info("保存路径platformCert:{}", certPath);
            //将生成的证书写入指定路径,文件名为:cert.pem
            FileOutputStream fos = new FileOutputStream(certPath);
            fos.write(publicKey.getBytes());
            fos.close();
            // 获取平台证书序列号
            X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
            return certificate.getSerialNumber().toString(16).toUpperCase();
        } catch (Exception e) {
            log.error("写入证书错误:{}", e);
            return e.getMessage();
        }
    }

    /**
     * 保存订单的支付通知明文
     *
     * @param plainText 纯文本
     */
    private void savePayPlainText(String plainText) {
        JSONObject jsonObject = JSONUtil.parseObj(plainText);
        //这个就是发起订单时的那个订单号
        String outTradeNo = jsonObject.getStr("out_trade_no");
        //todo 把微信支付回调的明文消息存进数据库,方便后续校验查看
        log.info("业务订单号,outTradeNo:{}", outTradeNo);
        //todo 把微信支付后需要处理的具体业务处理了
    }

    /**
     * 获取证书序列号
     *
     * @return String
     */
    private String getSerialNumber() {
        log.info("验证证书路径:{}", apiClientCertPath);
        // 获取证书序列号
        X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(apiClientCertPath));
        String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
        log.info("获取证书序列号:{},", serialNo);
        return serialNo;
    }
}

2.4.调用成功后接口返回示例

doUnifiedOrder接口参数返回示例
{
    "timeStamp": "",
    "signType": "RSA",
    "package": "prepay_id=",
    "paySign": "",
    "nonceStr": "",
    "appId": ""
}
createPlatformCert接口参数返回示例
{
    "data": [
        {
            "effective_time": "2023-10-17T17:25:40+08:00",
            "encrypt_certificate": {
                "algorithm": "",
                "associated_data": "",
                "ciphertext": "",
                "nonce": ""
            },
            "expire_time": "2028-10-15T17:25:40+08:00",
            "serial_no": ""
        }
    ]
}
query接口参数返回示例
{
    "amount": {
        "currency": "CNY",
        "payer_currency": "CNY",
        "payer_total": 1,
        "total": 1
    },
    "appid": "",
    "attach": "",
    "bank_type": "",
    "mchid": "",
    "out_trade_no": "",
    "payer": {
        "openid": ""
    },
    "promotion_detail": [],
    "success_time": "2023-10-18T17:21:04+08:00",
    "trade_state": "",
    "trade_state_desc": "订单发生过退款,退款详情请查询退款单",
    "trade_type": "JSAPI",
    "transaction_id": ""
}

3.最后

到此为止微信支付后端Java编写完毕,完成统一下单接口、回调接口、查询账单接口、下载平台证书接口

在这感谢:https://blog.csdn.net/weixin_41451078/article/details/125134636提供思路
以及IJPay全体开发人员:https://gitee.com/javen205/IJPay

Java 实现微信小程序 V3 版本的支付需要依赖微信支付 V3 版本的 Java SDK。具体实现步骤如下: 1. 引入微信支付 V3 版本的 Java SDK。 ``` <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apiv3-sdk</artifactId> <version>1.1.0</version> </dependency> ``` 2. 构建微信支付 V3 版本的配置信息。 ``` WechatPayConfig config = new WechatPayConfig.Builder() .appId("微信小程序的 AppID") .mchId("商户号") .apiKey("API 密钥") .certSerialNo("API 证书序列号") .privateKey("API 密钥对应的私钥") .build(); ``` 3. 构建微信支付 V3 版本的 API 客户端。 ``` WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(config); WechatPayHttpClient client = builder.build(); ``` 4. 构建微信支付 V3 版本的请求参数。 ``` WxPayNativeOrderRequest request = new WxPayNativeOrderRequest(); request.setAppid("微信小程序的 AppID"); request.setMchid("商户号"); request.setOutTradeNo("商户订单号"); request.setTotal("订单总金额"); request.setDescription("订单描述"); request.setNotifyUrl("支付回调地址"); ``` 5. 调用微信支付 V3 版本的 API 完成支付。 ``` WxPayNativeOrderResult result = client.execute(request); ``` 需要注意的是,微信支付 V3 版本的 API 接口和参数有所变化,需要根据官方文档进行调整。同时,需要在微信支付后台配置小程序支付回调地址,并保证服务器能够正常接收和处理支付回调信息。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值