背景:
随着最近小程序的流行,很多人开始使用小程序唤起微信支付本文以小程序对接全流程进行讲解。
环境:
本文采用java1.8 ,maven 搭建项目,springboot 2.5.15
利用微信最新的sdk "微信支付 APIv3 Java SDK"
目前的sdk版本
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
概念
merchantId - 商户id 只要有心就可以看到的 ;位置:账户中心->商户信息->微信支付商户号
privateKeyPath - 商户API私钥路径 一会讲解怎么生成
merchantSerialNumber - 这个在证书的生成之后才会有;位置:账户中心->API安全->API证书管理
apiV3Key - 如果使用v3的话需要设置 秘钥
appId - 就是你要绑定的按个小程序或者公众号等(据说不同主体商户号和appid绑定会有问题,但是本次介入的时候直接绑定了)。;位置: 产品中心->AppID账号管理
开发准备
地址:下载并配置商户证书 - 通用规则 | 微信支付商户文档中心 (qq.com)
注意事项:
- 第一登录话需要设置api操作密码
- 建议先下载并配置商户号 具体按照流程操作就行了
- 配置API key ,建议v3 ,记录APIv3 秘钥
- 需要再 商户平台上绑定appid,绑定之后 在appid的管理平台进行授权,再次查看商户平台是否绑定成功。
开发
- 由于调用平次较多,所有利用spring容器,将类进行容器管理
- 准备配置
@Slf4j
@Service
public class JsapiInitService {
@Value("${pay.wxmin.merchantId}")
public String merchantId ;
/**
* 商户API私钥路径
*/
@Value("${pay.wxmin.privateKeyPath}")
public String privateKeyPath ;
/**
* 商户证书序列号
*/
@Value("${pay.wxmin.merchantSerialNumber}")
public String merchantSerialNumber ;
/**
* 商户APIV3密钥
*/
@Value("${pay.wxmin.apiV3Key}")
public String apiV3Key ;
// 该方法返回 小程序唤起支付的所需信息,也有只返回预下单单号,但是小程序生成所需信息需要商户的私钥,这样不利于安全管理
public JsapiServiceExtension getJsapiServiceExtension(){
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
return new JsapiServiceExtension.Builder().config(config).build();
}
//退款的请求
public RefundService getRefundService() {
Config config =
null;
config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
// 构建service
return new RefundService.Builder().config(config).build();
}
// 回调解密处理
public NotificationParser getNotificationParser(){
NotificationConfig config = null;
config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
// 初始化 NotificationParser
return new NotificationParser(config);
}
}
- 发起支付
小程序下单
String payOrderId = orderId+ System.currentTimeMillis();
payOrderId = payOrderId.substring(0, 32);
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(amount);
request.setAmount(amount);
request.setAppid(appId);
request.setMchid(merchantId);
request.setDescription("测试商品标题");
request.setNotifyUrl(notifyUrl);
request.setOutTradeNo(payOrderId);
Payer payer = new Payer();
payer.setOpenid(sysUser.getOpenid());
request.setPayer(payer);
try {
PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = jsapiInitService.getJsapiServiceExtension().prepayWithRequestPayment(request);
JSONObject result = new JSONObject();
result.put("appId", prepayWithRequestPaymentResponse.getAppId());
result.put("timeStamp", prepayWithRequestPaymentResponse.getTimeStamp());
result.put("nonceStr", prepayWithRequestPaymentResponse.getNonceStr());
result.put("package", prepayWithRequestPaymentResponse.getPackageVal());
result.put("signType", prepayWithRequestPaymentResponse.getSignType());
result.put("paySign", prepayWithRequestPaymentResponse.getPaySign());
return AjaxResult.success(result);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("微信支付预下单失败,message:" + e.getMessage());
}
- 退款
注意: 本次返回状态 不出意外为:PROCESSING
退款申请
CreateRequest request = new CreateRequest();
request.setOutTradeNo(bdRefundRecord.getPayNo());
request.setOutRefundNo(bdRefundRecord.getRefundNo());
AmountReq amountReq = new AmountReq();
amountReq.setRefund(amount);
amountReq.setCurrency("CNY");
amountReq.setTotal(amount);
request.setAmount(amountReq);
request.setNotifyUrl(refundNotifyUrl);
log.info("发起微信退款详细参数:{}", request);
Refund refund = refundService.create(request);
if (Objects.equals(refund.getStatus().name(), "PROCESSING")) {
}
- 支付退款回调
支付通知
回调需要读取header中数据,如果controller 接受request
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try {
ServletInputStream inputStream = request.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
String wechatSignature = request.getHeader("Wechatpay-Signature");
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
String wechatTimestamp = request.getHeader("Wechatpay-Timestamp");
String wechatpaySignatureType = request.getHeader("Wechatpay-Signature-Type");
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(wechatpayNonce)
.signature(wechatSignature)
.timestamp(wechatTimestamp)
.body(getRequestBody(request))
.build();
// 以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = jsapiInitService.getNotificationParser().parse(requestParam, Transaction.class);
// 以退款通知回调为例,验签、解密并转换成 Transaction
RefundNotification transaction = jsapiInitService.getNotificationParser().parse(requestParam, RefundNotification.class);;
- 附带工具
对时间处理
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
ZonedDateTime dateTime = ZonedDateTime.parse(transaction.getSuccessTime(), formatter);
Date date = Date.from(dateTime.toInstant());
总结
- 本次开发需要前后端进行配合完成, 下单环境可以提前完成,交给小程序,但是唤起支付需要小程序的配合,这时候可以完成 发起之后和回调的开发。 这么说因为当唤起支付之后,其他的任务也就一并而来,导致自己工期不足。
- 本次开发也是参考了很多,找了很多资料,但大多数意义不大,这次从开发角度来说 应该是全的。不全不理解我也没有办法,除非我给你写
- 没了