官方文档:
基础知识介绍
准备条件:
-
申请注册微信小程序并完成企业认证;(请参阅官方文档)
-
注册开通微信支付商户;(请参阅官方文档)
SpringBoot 集成接入 Api v3
支付场景流程-商品购买下单为例
- 小程序端选中商品调用后端创建商品订单接口,返回订单编号到小程序端;
- 小程序端再带着 订单编号 去调用后端小程序支付下单接口,接口会去调用微信支付服务后台生成预支付交易单,返回正确的 预支付参数;
- 小程序端再拿到预支付参数 直接通过 微信支付官方提供的小程序方法调起小程序支付;
- 小程序端支付成功后 微信支付平台会下发一条支付通知 到配置好的支付回调接口;
- 支付回调接口收到通知后,再根据支付状态完成 后续的系统业务逻辑,如修改订单状态等等。
后端代码
- 引入maven 依赖
<!--微信支付APIv3-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.14</version>
</dependency>
- 配置文件内添加支付配置
wx:
# 微信小程序配置
appid: 111xxxxx2222
secret: 222xxxxxx1111
pay:
#应用id(小程序id)
appId: wxxxxxxxxxxxxxxx0d
#商户号
merchantId: 1xxxxxxx00
#商户API私钥
privateKey: apiclient_key.pem
#商户证书序列号
merchantSerialNumber: 24xxxxx9D4xxx0xxxxxDCCCFDxxxxx87EFxxxxC1
#商户APIv3密钥
apiV3Key: qmxxxxxuiz71y2u3h45jlknbv89olzx
#支付(回调)通知地址 必须是外网可访问的https域名地址
payNotifyUrl: http://av2gta.natappfree.cc/client/payNotify
#退款通知地址
refundNotifyUrl: https://xxx.xxx.xxx.xxx/xxxx/xxxx/xxxx/openapi/wx/refundNotify
- 配置文件相关
代理配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author:wwf
* @Package:com.xxx.xxx.config
* @Project:fishing-manage
* @name:WxPayConfig
* @Date:2024/10/28 14:50
* @Filename:WxPayConfig
*/
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
private String appId;
/**
* 商户号
*/
private String merchantId;
/**
* 商户API私钥
*/
private String privateKey;
/**
* 商户证书序列号
*/
private String merchantSerialNumber;
/**
* 商户APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知地址
*/
private String payNotifyUrl;
/**
* 退款通知地址
*/
private String refundNotifyUrl;
}
配置文件初始化商户配置
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.PemUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import javax.annotation.Resource;
import java.io.IOException;
/**
* @Author:wwf
* @Package:com.xxx.xxx.config
* @Project:fishing-manage
* @name:WxPayAutoCertificateConfig
* @Date:2024/10/28 14:52
* @Filename:WxPayAutoCertificateConfig
*/
@Configuration
public class WxPayAutoCertificateConfig {
@Resource
private WxPayConfig wxPayConfig;
/**
* 初始化商户配置
* @return
*/
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig() throws IOException {
RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKey(PemUtil.loadPrivateKeyFromPath(new ClassPathResource(wxPayConfig.getPrivateKey()).getURL().getPath()))
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
return config;
}
}
- 小程序支付下单
@Resource
private WxPayConfig wxPayConfig;
@Autowired
private RSAAutoCertificateConfig rsaAutoCertificateConfig;
/**
* 小程序支付下单
*
* @param req
* @return
*/
public PrepayWithRequestPaymentResponse createOrder(CreateOrderParam req) {
String orderNo = req.getOutTradeNo();
Integer totalFee = req.getTotalPrice();
String openId = req.getOpenId();
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
// 不填默认为RSA
.signType("RSA")
.build();
PrepayWithRequestPaymentResponse response = new PrepayWithRequestPaymentResponse();
try {
PrepayRequest request = new PrepayRequest();
request.setAppid(wxPayConfig.getAppId());
request.setMchid(wxPayConfig.getMerchantId());
request.setDescription("描述");
request.setOutTradeNo(orderNo);
// 支付成功后的回调地址
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
Amount amount = new Amount();
amount.setTotal(totalFee);
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(openId);
request.setPayer(payer);
log.info("请求预支付下单,请求参数:{}", JSONObject.toJSONString(request));
// 调用预下单接口
response = service.prepayWithRequestPayment(request);
log.info("订单【{}】发起预支付成功,返回信息:{}", orderNo, response);
} catch (HttpException e) { // 发送HTTP请求失败
log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getMessage());
} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
log.error("微信下单服务状态错误,错误信息:{}", e.getMessage());
throw new ServiceException("下单失败");
} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
throw new ServiceException("下单失败");
}
return response;
}
- 支付回调(通知)
@Resource
private OrdersMapper ordersMapper;
public String payNotify(HttpServletRequest request) {
log.info("------收到支付通知------");
// 请求头WeChat-Signature
String signature = request.getHeader("Wechatpay-Signature");
// 请求头WeChat-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头WeChat-Timestamp
String timestamp = request.getHeader("Wechatpay-Timestamp");
// 微信支付证书序列号
String serial = request.getHeader("Wechatpay-Serial");
// 签名方式
String signType = request.getHeader("Wechatpay-Signature-Type");
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(ServletUtil.getBody(request))
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
// 以支付通知回调为例,验签、解密并转换成 Transaction
log.info("验签参数:{}", requestParam);
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("验签成功!-支付回调结果:{}", transaction.toString());
Map<String, String> returnMap = new HashMap<>(2);
returnMap.put("code", "FAIL");
returnMap.put("message", "失败");
//todo 修改订单前,建议主动请求微信查询订单是否支付成功,防止恶意post out_trade_no
Orders orders = ordersMapper.selectOne(new LambdaQueryWrapper<Orders>().eq(Orders::getOrderNo, transaction.getOutTradeNo()));
if (Objects.isNull(orders)) {
log.error("订单不存在,非法调用");
return JSONObject.toJSONString(returnMap);
}
//判断订单存在,但是不为待支付
if (!orders.getOrderStatus().getKey().equals(UserOrderStatusEnum.PENDING.getKey())) {
log.error("订单非待支付状态");
return JSONObject.toJSONString(returnMap);
}
//获取支付状态
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
//如果微信响应 非 支付成功,不做处理 直接返回
if (!tradeState.equals(Transaction.TradeStateEnum.SUCCESS)) {
log.error("订单未支付成功,状态为【{}】", tradeState);
return JSONObject.toJSONString(returnMap);
}
// 如果微信响应支付成功,且当前状态为待支付,修改订单状态为已支付,并保存交易id TransactionId
orders.setOrderStatus(UserOrderStatusEnum.PAID);
orders.setPayTime(new Date());
orders.setTransactionId(transaction.getTransactionId());
//todo 需要去修改订单相关所有业务
ordersMapper.updateById(orders);
returnMap.put("code", "SUCCESS");
returnMap.put("message", "成功");
return JSONObject.toJSONString(returnMap);
}
小程序端代码(uniApp)
// 调用后端小程序下单接口获取支付参数
// 省略 ...
//调起支付
uni.requestPayment({
"provider": "wxpay",//支付方式
"timeStamp": "1414561699",//时间戳
"nonceStr":"5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package":"prepay_id=wx201410272009395522657a690389285100",
"signType":"RSA",//签名算法,需要与后台下单时一致
"paySign":"oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",签名
success: function (resSuccess) {
//支付成功后的操作
//todo...
},
fail: function (err) {
//支付失败后的操作
//todo...
}
)
over …