前言
最近做了一个小程序对接微信支付的需求,查看微信支付文档,还是感觉有点凌乱,所以做一个统一整理,供大家参考。
API参考官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_3.shtml
支付流程
重点步骤说明:
- 用户下单发起支付,商户可通过JSAPI下单创建支付订单。
- 商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档。
- 用户支付成功后,商户可接收到微信支付支付结果通知支付通知API。
- 商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
开发步骤
1 接入前准备
1.1 微信支付配置申请
详细操作流程参考官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml#part-1
配置完成需要以下信息:
- APPID
- 商户号(mchid)
- 商户API私钥(apiclient_key.pem)
- 商户证书序列号
- 商户APIv3密钥
1.2 引入开发库
Gradle
implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.10'
Maven
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.10</version>
</dependency>
1.3 创建配置
配置信息
#微信支付配置
wx:
pay:
appId:
merchantId:
privateKey:
merchantSerialNumber:
apiV3Key:
payNotifyUrl:
配置类
package com.xh.server.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author yupf
* @description 微信支付配置类
* @date 2023/7/26 09:30
*/
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
//APPID
private String appId;
//mchid
private String merchantId;
//商户API私钥
private String privateKey;
//商户证书序列号
private String merchantSerialNumber;
//商户APIv3密钥
private String apiV3Key;
//支付通知地址
private String payNotifyUrl;
}
1.4 初始化商户配置
package com.xh.server.config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* @author yupf
* @description 微信支付证书自动更新配置
* @date 2023/7/26 14:37
*/
@Configuration
public class WxPayAutoCertificateConfig {
@Resource
private WxPayConfig wxPayConfig;
/**
* 初始化商户配置
* @return
*/
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig() {
RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKey(wxPayConfig.getPrivateKey())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
return config;
}
}
2 JSAPI下单
文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
@ApiOperation(value = "预支付订单", notes = "预支付订单")
@PostMapping("/create")
public R<PrepayWithRequestPaymentResponse> createOrder(@Validated @RequestBody CreateOrderReq req) {
//创建初始化订单
***
//请求微信支付相关配置
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA") // 不填默认为RSA
.build();
PrepayWithRequestPaymentResponse response = new PrepayWithRequestPaymentResponse();
try {
PrepayRequest request = new PrepayRequest();
request.setAppid(wxPayConfig.getAppId());
request.setMchid(wxPayConfig.getMerchantId());
request.setDescription(order.getDescription());
request.setOutTradeNo(order.getOrderNo());
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
Amount amount = new Amount();
amount.setTotal(order.getAmount().multiply(new BigDecimal("100")).intValue());
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(req.getWxOpenId());
request.setPayer(payer);
log.info("请求预支付下单,请求参数:{}", JSONObject.toJSONString(request));
// 调用预下单接口
response = service.prepayWithRequestPayment(request);
log.info("订单【{}】发起预支付成功,返回信息:{}", order.getOrderNo(), response);
} catch (HttpException e) {
// 发送HTTP请求失败
log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getHttpRequest());
return R.error().message("下单失败");
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
return R.error().message("下单失败");
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
return R.error().message("下单失败");
}
//更新订单状态
***
return R.ok().data(response);
}
这里有几个注意点:
- 小程序预支付下单需要获取用户的openid;
- 预支付下单请求及返回类所在包为com.wechat.pay.java.service.payments.jsapi;
- PrepayWithRequestPaymentResponse对应的是wx.requestPayment接口中的请求参数,前端可直接使用;
3 查询订单
文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_2.shtml
微信提供了两种查询订单方式:
- 微信支付订单号查询;
- 商户订单号查询
@ApiOperation(value = "根据支付订单号查询订单", notes = "根据支付订单号查询订单")
@PostMapping("/queryOrder")
public R queryOrder(@Validated @RequestBody QueryOrderReq req) {
QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid(wxPayConfig.getMerchantId());
queryRequest.setTransactionId(req.paymentNo);
try {
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA") // 不填默认为RSA
.build();
Transaction result = service.queryOrderById(queryRequest);
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
}
} catch (ServiceException e) {
log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
return R.error();
}
return R.ok();
}
@ApiOperation(value = "根据商户订单号查询订单", notes = "根据商户订单号查询订单")
@PostMapping("/queryOrder")
public R queryOrder(@Validated @RequestBody QueryOrderReq req) {
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wxPayConfig.getMerchantId());
queryRequest.setOutTradeNo(req.orderNo);
try {
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA") // 不填默认为RSA
.build();
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
}
} catch (ServiceException e) {
log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
return R.error();
}
return R.ok();
}
4 支付通知
文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml
回调地址notify_url设置规范:
- 异步接收微信结果通知回调地址,通知url必须为外网可访问的url;
- 不能携带任何参数;
- 公网域名必须为https,现使用http域名能正常接收回调的用户,建议更换https,避免后期出现回调通知无法接收的情况;
- 不支持携带端口号
其他注意事项详见文档:https://pay.weixin.qq.com/wiki/doc/apiv3/Practices/chapter1_1_5.shtml
@ApiOperation(value = "预支付-回调")
@PostMapping("/payNotify")
public synchronized String payNotify(HttpServletRequest request) throws IOException {
log.info("------收到支付通知------");
// 请求头Wechatpay-Signature
String signature = request.getHeader("Wechatpay-Signature");
// 请求头Wechatpay-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头Wechatpay-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(HttpServletUtils.getRequestBody(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", "失败");
if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", transaction.getOutTradeNo(), transaction.getTransactionId());
return JSONObject.toJSONString(returnMap);
}
//修改订单前,建议主动请求微信查询订单是否支付成功,防止恶意post
***
//修改订单信息
***
returnMap.put("code", "SUCCESS");
returnMap.put("message", "成功");
return JSONObject.toJSONString(returnMap);
}
package com.xh.server.util;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author yupf
* @date 2023/07/26 上午9:17
**/
public class HttpServletUtils {
/**
* 获取请求体
*
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
}
总结
以上便是小程序对接微信支付的全部流程,其他如APP或者H5对接流程大同小异,只不过引用的包有所不同,建议下载开发库wechatpay-java详细阅读以下源码。