微信支付|SpringBoot集成微信小程序创建订单&支付&退款(apiV3+SDK保姆级教程)_springboot集成微信支付创建订单&支付&退款
注:本文代码亲测可用!可复制直接使用!减少工作量!
微信小程序支付官网接口文档:https://pay.weixin.qq.com/doc/v3/merchant/4012791911
本文使用【微信支付 APIv3 Java SDK(wechatpay-java) 】实现。
官方微信支付 APIv3 Java SDK https://github.com/wechatpay-apiv3/wechatpay-java
前置条件
- Java 1.8+。
- 成为微信支付商户。
- 商户 API 证书:指由商户申请的,包含证书序列号、商户的商户号、公司名称、公钥信息的证书。
- 商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
- APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。
商户平台截图
业务流程图
重点步骤说明:
步骤4: 用户下单发起支付,商户可通过JSAPI下单创建支付订单。
步骤9: 商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档 (opens new window)。
步骤16: 用户支付成功后,商户可接收到微信支付支付结果通知支付通知API。
步骤21: 商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
具体实现步骤如下
1.前端弹框发起支付,调用【预支付订单/统一下单(/wxMiniappPay/createOrder)】接口取得预支付参数。
2.前端将上一步获得的参数,在小程序中调用支付API(wx.requestPayment)发起微信支付,用户输入支付密码后即可成功支付。
3.用户成功支付后,微信将通过【支付回调(/wxMiniappPay/payNotify)】接口推送支付成功的信息给后端,后端根据业务进行操作即可。
4.前端在用户输入密码支付后,调用【根据商户订单号查询订单(/wxMiniappPay/queryOrderByOutTradeNo)】接口,查看支付信息,按照自己的业务进行信息展示。
5.后端通过定时任务,调用【关闭订单(/wxMiniappPay/closeOrder)】接口,对未成功支付的订单进行关闭。
6.如果需要退款操作,前端调用【退款申请(/wxMiniappPay/refund)】接口,进行退款操作。
7.成功退款后,微信会通过【微信小程序退款回调】接口,推送退款状态信息,后端同步退款状态。
8.后端通过定时任务,调用【查询单笔退款(通过商户退款单号)(/wxMiniappPay/queryByOutRefundNo)】接口,同步退款状态。
上述场景中,后端根据业务需要采用数据锁进行并发控制。这里不做赘述!
微信小程序支付实现
pom.xml加入以下依赖
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-java</artifactId><version>0.2.15</version></dependency>
application.yml配置
# 微信商户wx: miniapp: appid: wxe*******f6 # 微信小程序appid secret: 3c80b11*********1aed7 # 微信小程序密钥 merchantId: 16******99 # 商户号 privateKeyPath: /wechat_pay/private_keys/apiclient_key.pem # 商户API私钥路径(测试环境) merchantSerialNumber: 73CF06******EE7EAB3 # 商户API证书序列号 apiV3Key: aB3dE8********WxYzZ6 # 商户APIV3密钥 payNotifyUrl: 域名/wxMiniappPay/payNotify # 支付通知地址(测试环境) refundNotifyUrl: 域名/wxMiniappPay/refundNotify # 退款通知地址(测试环境)
微信小程序支付配置WxPayConfig
package com.github.config.wx;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * * 微信小程序支付配置 *
* * @author songfayuan * @date 2024/9/30 15:59 */@Data@Component@ConfigurationProperties(prefix = \"wx.miniapp\")public class WxPayConfig { /** * 微信小程序的 AppID */ private String appid; /** * 微信小程序的密钥 */ private String secret; /** * 商户号 */ private String merchantId; /** * 商户API私钥路径 */ private String privateKeyPath; /** * 商户证书序列号 */ private String merchantSerialNumber; /** * 商户APIV3密钥 */ private String apiV3Key; /** * 支付通知地址 */ private String payNotifyUrl; /** * 退款通知地址 */ private String refundNotifyUrl;}
微信支付证书自动更新配置WxPayAutoCertificateConfig
package com.github.config.wx;import com.wechat.pay.java.core.Config;import com.wechat.pay.java.core.RSAAutoCertificateConfig;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;/** * * 微信支付证书自动更新配置 * 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错 *
* * @author songfayuan * @date 2024/9/30 15:57 */@Slf4j@Configurationpublic class WxPayAutoCertificateConfig { @Resource private WxPayConfig wxPayConfig; /** * 初始化商户配置 * @return */ @Bean public Config rsaAutoCertificateConfig() { // 这里把Config作为配置Bean是为了避免多次创建资源,一般项目运行的时候这些东西都确定了 // 具体的参数改为申请的数据,可以通过读配置文件的形式获取 Config config = new RSAAutoCertificateConfig.Builder() .merchantId(wxPayConfig.getMerchantId()) .privateKeyFromPath(wxPayConfig.getPrivateKeyPath()) .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber()) .apiV3Key(wxPayConfig.getApiV3Key()) .build();// Config config = new RSAAutoCertificateConfig.Builder()// .merchantId(wxPayConfig.getMerchantId())// .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())// .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())// .apiV3Key(wxPayConfig.getApiV3Key())// .build(); log.info(\"初始化微信支付商户配置完成...\"); return config; }}
微信小程序支付 前端控制器Controller
package com.github.modules.miniapp.controller;import com.github.common.utils.BaseController;import com.github.common.utils.Response;import com.github.modules.miniapp.entity.CreateOrderReq;import com.github.modules.miniapp.entity.QueryOrderReq;import com.github.modules.miniapp.entity.RefundOrderReq;import com.github.modules.miniapp.service.WxMiniappPayService;import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/** *
* 微信小程序支付 前端控制器 *
* * @author songfayuan * @date 2024/9/14 14:22 */@Slf4j@RestController@RequestMapping(\"/wxMiniappPay\")public class WxMiniappPayController extends BaseController { @Autowired private WxMiniappPayService wxMiniappPayService; /** * 预支付订单/统一下单 * @param req * @return */ @RequestMapping(\"/createOrder\") public Response<PrepayWithRequestPaymentResponse> createOrder(@Validated @RequestBody CreateOrderReq req) { log.info(\"------预支付订单/统一下单------\"); //微信小程序登录用户openid,用户标识 说明:用户在商户appid下的唯一标识。 String openid = getOpenid(); req.setWxOpenId(openid); return this.wxMiniappPayService.createOrder(req); } /** * 支付回调 * * @param request * @return * @throws IOException */ @PostMapping(\"/payNotify\") public String payNotify(HttpServletRequest request) throws IOException { log.info(\"------收到微信支付回调通知------\"); return this.wxMiniappPayService.payNotify(request); } /** * 根据支付订单号查询订单 ** 主动查询订单结果 * 支付订单号:业务侧的订单号 ** * @param req * @return */
@PostMapping(\"/queryOrder\") public Response queryOrder(@Validated @RequestBody QueryOrderReq req) { log.info(\"------根据支付订单号查询订单------\"); return this.wxMiniappPayService.queryOrder(req); } /** * 根据商户订单号查询订单 ** 主动查询订单结果 * 支付订单号:商户订单号 ** * @param req * @return */
@PostMapping(\"/queryOrderByOutTradeNo\") public Response queryOrderByOutTradeNo(@Validated @RequestBody QueryOrderReq req) { log.info(\"------根据商户订单号查询订单------\"); return this.wxMiniappPayService.queryOrderByOutTradeNo(req); } /** * 关闭订单 * @param req * @return */ @PostMapping(\"/closeOrder\") public Response closeOrder(@Validated @RequestBody QueryOrderReq req) { log.info(\"------微信小程序支付关闭订单------\"); return this.wxMiniappPayService.closeOrder(req); } /** * 退款申请 * @param req * @return */ @PostMapping(\"/refund\") public Response refund(@Validated @RequestBody RefundOrderReq req) { log.info(\"------微信支付退款------\"); return this.wxMiniappPayService.refund(req); } /** * 查询单笔退款(通过商户退款单号) * @param outRefundNo 商户退款单号 * @return */ @GetMapping(\"/queryByOutRefundNo\") public Response queryByOutRefundNo(String outRefundNo) { log.info(\"------微信支付查询单笔退款------\"); return this.wxMiniappPayService.queryByOutRefundNo(outRefundNo); } /** * 微信小程序退款回调 * @param request * @return * @throws Exception */ @PostMapping(\"/refundNotify\") public String refundNotify(HttpServletRequest request) throws Exception { log.info(\"------微信支付微信小程序退款回调------\"); return this.wxMiniappPayService.refundNotify(request); }}微信小程序支付服务类Service
package com.github.modules.miniapp.service;import com.github.common.utils.Response;import com.github.modules.miniapp.entity.CreateOrderReq;import com.github.modules.miniapp.entity.QueryOrderReq;import com.github.modules.miniapp.entity.RefundOrderReq;import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/** *
* 微信小程序支付服务类 *
* * @author songfayuan * @date 2024/9/14 14:22 */public interface WxMiniappPayService { /** * 预支付订单/统一下单 * @param req * @return */ Response<PrepayWithRequestPaymentResponse> createOrder(CreateOrderReq req); /** * 支付回调 * @param request * @return * @throws IOException */ String payNotify(HttpServletRequest request) throws IOException; /** * 根据支付订单号查询订单 * @param req * @return */ Response queryOrder(QueryOrderReq req); /** * 根据商户订单号查询订单 * @param req * @return */ Response queryOrderByOutTradeNo(QueryOrderReq req); /** * 关闭订单 * @param req * @return */ Response closeOrder(QueryOrderReq req); /** * 退款 * @param req * @return */ Response refund(RefundOrderReq req); /** * 查询单笔退款(通过商户退款单号) * @param outRefundNo 商户退款单号 * @return */ Response queryByOutRefundNo(String outRefundNo); /** * 微信小程序退款回调 * @param request * @return */ String refundNotify(HttpServletRequest request)