必读信息
1.对接信息
| 对接场景 | 用户在小程序下单,使用微信支付付款 |
|---|---|
| 对接商户类型 | 直连商户 |
| 对接支付版本 | v3 |
| 后端架构 | Java+SpringBoot |
| 对接时间 | 2023年9月 |
2.对接流程
共分为4个大流程,主要开发工作量在后端。
2.1 流程1:注册微信小程序、微信支付商户号
步骤略
2.2 流程2:登录微信商户、小程序后台,进行配置
| 登录微信商户,设置v3秘钥(此步骤可得到:v3秘钥开发需要,记下来) | |
|---|---|
| 申请证书(此步骤可得到:证书序列号,查看证书序列号查看 myssl.com/cert_decode… | |
| 关联,微信商户发起关联;小程序平台确认授权。 | |
| 已申请状态 |
2.3 流程3:后端接口开发
根据调用流程和业务需求,后端需提供接口
| 下单接口 | 向微信平台请求下单参数,再给到小程序前端,然后前端拉起微信支付弹窗,用户付款;参考 JSAPI下单 |
|---|---|
| 支付通知回调 | 用于更新自己业务的订单状态。参考支付通知;支付回调需要提供https域名,本地调试可使用cpolar实现内网穿透进行测试(免费) |
| 查询订单接口(非必须) | 场景:当用户主动查单时 |
后端开发流程
集成官方SDK wechatpay-java
1、maven添加配置
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
2、创建配置类
import lombok.Data;
/**
* 配置
*/
@Data
public class WxV3PayConfig {
/**
* 微信回调通知地址
*/
public static String PAY_BACK_URL = "";
//平台证书序列号
public static String MCH_SERIAL_NO = "";
//appID
public static String APP_ID = "";
//商户id
public static String MCH_ID = "";
// API V3密钥
public static String API_V3_KEY = "";
// 商户私钥 打开apiclient_key.pem拷贝出来的字符串
public static String PRIVATE_KEY = "";
}
/**
* 动态下单参数
*/
@Data
public class PreOrderDynamicParam {
/**
* 订单号(业务)
*/
String outTradeNo;
/**
* 用户openId
*/
String openId;
/**
* 订单描述
*/
String description;
/**
* 订单总金额,单位为分
*/
int total;
public PreOrderDynamicParam(String outTradeNo, String openId, String description, int total) {
this.outTradeNo = outTradeNo;
this.openId = openId;
this.description = description;
this.total = total;
}
}
/**
* 微信支付签名对象
*/
@Data
public class WechatPaySign {
/**
* 预支付编号
*/
private String prepayId;
/**
* 时间戳
*/
private String timeStamp;
/**
* 随机字符串
*/
private String nonceStr;
private String packageStr;
/**
* 支付签名
*/
private String sign;
}
3、创建服务类和实现类
import com.dxda.basic.entity.PreOrderDynamicParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.SortedMap;
/**
* 微信支付
*/
public interface IWxPayService {
/**
* JSAPI下单
*
* @return
*/
SortedMap<String, String> jsApiOrder(PreOrderDynamicParam preOrderDynamicParam) throws Exception;
/**
* 支付回调
*
* @param request
* @return
* @throws IOException
*/
ResponseEntity callback(HttpServletRequest request) throws IOException;
/**
* 查询订单,根据商户订单号
*
* @return
*/
Transaction queryWxPayOrderOutTradeNo(String transNo);
/**
* 关闭订单
*
* @return
*/
void closePay(String outTradeNo);
}
import com.alibaba.fastjson.JSON;
import com.dxda.basic.entity.PreOrderDynamicParam;
import com.dxda.basic.service.UdiOrderService;
import com.dxda.common.utils.uuid.RandomUtils;
import com.dxda.wx.service.IWxPayService;
import com.dxda.wx.service.config.WechatPaySign;
import com.dxda.wx.service.config.WxV3PayConfig;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author lws
* @since 2023-9-20
*/
@Slf4j
@Service
public class WxPayServiceImpl implements IWxPayService {
@Resource
UdiOrderService udiOrderService;
/**
* 微信支付
*/
@Override
@Transactional
public SortedMap<String, String> jsApiOrder(PreOrderDynamicParam preOrderDynamicParam) throws Exception {
log.info("微信支付 >>>>>>>>>>>>>>>>> 金额:{}", preOrderDynamicParam.getTotal());
PrepayRequest request = new PrepayRequest();
SortedMap<String, String> params = new TreeMap<>();
Amount amount = new Amount();
amount.setTotal(preOrderDynamicParam.getTotal());
Payer payer = new Payer();
payer.setOpenid(preOrderDynamicParam.getOpenId());
request.setAmount(amount);
request.setPayer(payer);
request.setAppid(WxV3PayConfig.APP_ID);
request.setMchid(WxV3PayConfig.MCH_ID);
request.setDescription(preOrderDynamicParam.getDescription());
request.setNotifyUrl(WxV3PayConfig.PAY_BACK_URL);
request.setOutTradeNo(preOrderDynamicParam.getOutTradeNo());
PrepayResponse response = getJsapiService().prepay(request);
WechatPaySign sign = sign(response.getPrepayId());
params.put("trans_no", preOrderDynamicParam.getOutTradeNo());// 订单号(业务需要)
params.put("appId", WxV3PayConfig.APP_ID);
params.put("nonceStr", sign.getNonceStr());
params.put("package", "prepay_id=" + sign.getPrepayId());
params.put("signType", "RSA");
params.put("timeStamp", sign.getTimeStamp());
params.put("paySign", sign.getSign());
return params;
}
String getBodyUTF8(HttpServletRequest request) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
return stringBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return "";
}
/**
* 微信回调
*
* @param request
* @return
* @throws IOException
*/
@Transactional
public ResponseEntity callback(HttpServletRequest request) throws IOException {
log.info("微信回调v3 >>>>>>>>>>>>>>>>> ");
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(request.getHeader("Wechatpay-Serial"))
.nonce(request.getHeader("Wechatpay-Nonce"))
.timestamp(request.getHeader("Wechatpay-Timestamp"))
.signature(request.getHeader("Wechatpay-Signature"))
.body(getBodyUTF8(request))
.build();
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(WxV3PayConfig.MCH_ID)
.privateKey(WxV3PayConfig.PRIVATE_KEY)
.merchantSerialNumber(WxV3PayConfig.MCH_SERIAL_NO)
.apiV3Key(WxV3PayConfig.API_V3_KEY)
.build();
NotificationParser parser = new NotificationParser(config);
try {
// 验签、解密并转换成 Transaction(返回参数对象)
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("微信支付回调 成功,解析" + JSON.toJSONString(transaction));
// TODO 处理你的业务逻辑
// 处理成功,返回 200 OK 状态码
return ResponseEntity.status(HttpStatus.OK).build();
} catch (ValidationException e) {
log.error("sign verification failed", e);
log.error("微信支付回调v3java失败=" + e.getMessage(), e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
/**
* 关闭微信支付
*/
@Override
public void closePay(String outTradeNo) {
CloseOrderRequest closeRequest = new CloseOrderRequest();
closeRequest.setMchid(WxV3PayConfig.MCH_ID);
closeRequest.setOutTradeNo(outTradeNo);
getJsapiService().closeOrder(closeRequest);
}
@Override
public Transaction queryWxPayOrderOutTradeNo(String transNo) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(WxV3PayConfig.MCH_ID);
request.setOutTradeNo(transNo);
Transaction transaction = getJsapiService().queryOrderByOutTradeNo(request);
// TODO 处理你的业务逻辑
return transaction;
}
/**
* 创建小程序支付服务
*
* @return
*/
protected JsapiService getJsapiService() {
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(WxV3PayConfig.MCH_ID)
.privateKey(WxV3PayConfig.PRIVATE_KEY)
.merchantSerialNumber(WxV3PayConfig.MCH_SERIAL_NO)
.apiV3Key(WxV3PayConfig.API_V3_KEY)
.build();
config.createSigner().getAlgorithm();
return new JsapiService.Builder().config(config).build();
}
/**
* 获取支付签名
*
* @param prepayId
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public static WechatPaySign sign(String prepayId) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomUtils.randomEles(30);//随机字符串
String packageStr = "prepay_id=" + prepayId;
// 不能去除'.append("\n")',否则失败
String signStr = WxV3PayConfig.APP_ID + "\n" +
timeStamp + "\n" +
nonceStr + "\n" +
packageStr + "\n";
byte[] message = signStr.getBytes(StandardCharsets.UTF_8);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(PemUtil.loadPrivateKeyFromString(WxV3PayConfig.PRIVATE_KEY));
sign.update(message);
String signStrBase64 = Base64.getEncoder().encodeToString(sign.sign());
WechatPaySign wechatPaySign = new WechatPaySign();
wechatPaySign.setPrepayId(prepayId);
wechatPaySign.setTimeStamp(timeStamp);
wechatPaySign.setNonceStr(nonceStr);
wechatPaySign.setPackageStr(packageStr);
wechatPaySign.setSign(signStrBase64);
return wechatPaySign;
}
}
5、创建Controller
由于大家业务不同,此处省略示例。
2.4 流程4 前端小程序对接
前端调用后台接口jsApiOrder拿到返回参数,前端再调用wx.requestPayment即可拉起微信支付弹窗组件。参考
wx.requestPayment
(
{
"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(res){},
"fail":function(res){},
"complete":function(res){}
}
)
3.其他问题
3.1 验签异常排查
检查body顺序和编码;因素很多,可以参考下面
3.2 人民币单位
如果前端传递单位是元,需要转分去下单
3.3其他坑
-
不需要自己去下载APIv3 平台证书github.com/wechatpay-a…
-
不推荐集成wechatpay-apache-httpclient,验签失败和json包冲突问题(可能是个人项目原因)
《完》