SpringBoot 微信支付
参数配置
yaml
# application.yml
wechat:
# 微信支付
pay:
# APP ID
appId: "APP ID"
# 商户号
merchantId: "merchantId"
# 商户证书序列号
merchantSerialNumber: "merchantSerialNumber"
# 商户APIV3密钥
apiV3key: "apiV3key"
# 商户API私钥路径,绝对路径
privateKeyPath: "privateKeyPath"
# 微信证书,绝对路径
wechatPayCertificatePath: "wechatPayCertificatePath"
# 支付通知地址
payNotifyUrl: "payNotifyUrl"
# 退款通知地址
refundNotifyUrl: "refundNotifyUrl"
读取配置
java
// WxPayConfig.java
package com.demo.config.miniapp;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayConfig {
/**
* APP ID
*/
private String appId;
/**
* 商户号
*/
private String merchantId;
/**
* 商户证书序列号
*/
private String merchantSerialNumber;
/**
* 商户APIV3密钥
*/
private String apiV3key;
/**
* 商户API私钥路径
*/
private String privateKeyPath;
/**
* 微信证书
*/
private String wechatPayCertificatePath;
/**
* 支付通知地址
*/
private String payNotifyUrl;
/**
* 退款通知地址
*/
private String refundNotifyUrl;
}
微信支付回调通知请求头解析
java
// NotifyHeader.java
package com.demo.util.wechat.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
@Data
@Slf4j
public class NotifyHeader {
// Wechatpay-Signature
private String signature;
// Wechatpay-Nonce
private String nonce;
//Wechatpay-Timestamp
private String timestamp;
//Wechatpay-Serial
private String serial;
// Wechatpay-Signature-Type
private String signatureType;
public static NotifyHeader parseRequest(HttpServletRequest request) {
NotifyHeader notifyHeader = new NotifyHeader();
notifyHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
notifyHeader.setSignature(request.getHeader("Wechatpay-Signature"));
notifyHeader.setSerial(request.getHeader("Wechatpay-Serial"));
notifyHeader.setSignatureType(request.getHeader("Wechatpay-Signature-Type"));
notifyHeader.setTimestamp(request.getHeader("Wechatpay-Timestamp"));
log.info("notifyHeader: {}", notifyHeader);
return notifyHeader;
}
}
订单ID获取工具类
java
// UUIDUtil.java
package com.demo.util;
import java.util.UUID;
/**
* 获取uuid
*/
public class UUIDUtil {
/**
* 返回 36 位的uuid
* eg: 20021b46-f061-434e-ad39-57f6c55b59c2
*/
public static String getUUID() {
return UUID.randomUUID().toString();
}
/**
* 返回 32 位的uuid
* eg: 47a3690d724e4d408b3e2b952079b8e9
*/
public static String getOrderNo() {
return getUUID().replaceAll("-", "");
}
}
service定义
java
package com.demo.service.impl;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RSANotificationConfig;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
/**
* @ref https://github.com/wechatpay-apiv3/wechatpay-java
*/
@Service
@Slf4j
public class WechatPayService implements PayService {
@Autowired
private WechatPayConfig wechatPayConfig;
public Config getConfig() {
return new RSAConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.wechatPayCertificatesFromPath(wxPayConfig.getWechatPayCertificatePath())
.build();
}
/**
* 验签并解析支付通知数据
*
* @param request
* @return
* @throws IOException
*/
public <T> T parseNotify(HttpServletRequest request, Class<T> clazz) throws IOException {
NotifyHeader notifyHeader = NotifyHeader.parseRequest(request);
// body
String requestBody = RequestUtil.getRequestBody(request);
log.info("requestBody: {}", requestBody);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(notifyHeader.getSerial())
.nonce(notifyHeader.getNonce())
.signature(notifyHeader.getSignature())
.timestamp(notifyHeader.getTimestamp())
.body(requestBody)
.build();
NotificationConfig config = new RSANotificationConfig.Builder()
.certificatesFromPath(wxPayConfig.getWechatPayCertificatePath())
.apiV3Key(wxPayConfig.getApiV3key())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
// 以支付通知回调为例,验签、解密并转换成 Transaction
return parser.parse(requestParam, clazz);
}
/**
* Native支付预下单
*/
public String prepay(OrderEntity orderEntity) {
String uuid = UUIDUtil.getOrderNo();
PrepayRequest request = new PrepayRequest();
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
// 调用接口
Amount amount = new Amount();
amount.setTotal(100);
request.setAmount(amount);
request.setOutTradeNo(uuid);
request.setAppid(wxPayConfig.getAppId());
request.setMchid(wxPayConfig.getMerchantId());
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
request.setDescription(orderEntity.getServiceName());
// 调用下单方法,得到应答
NativePayService service = new NativePayService.Builder()
.config(this.getConfig())
.build();
PrepayResponse response = service.prepay(request);
// 使用微信扫描 code_url 对应的二维码,即可体验Native支付
return response.getCodeUrl();
}
@Override
public void handlePaySuccess(Transaction transaction) {
String outTradeNo = transaction.getOutTradeNo();
// 处理支付成功事件
}
@Override
public void wechatRefund(OrderWechatRefund orderWechatRefund) {
RefundService refundService = new RefundService
.Builder()
.config(this.getConfig())
.build();
CreateRequest request = new CreateRequest();
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
AmountReq amount = new AmountReq();
amount.setTotal(100));
amount.setRefund(100));
amount.setCurrency("CNY");
request.setAmount(amount);
request.setOutTradeNo("OutTradeNo");
request.setOutRefundNo("OutRefundNo");
request.setReason("退款原因");
request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl());
// 调用接口
try{
Refund refund = refundService.create(request);
} catch (ServiceException serviceException){
throw new AppException(serviceException.getErrorMessage());
}
}
@Override
public Transaction parseTransactionNotify(HttpServletRequest request) throws IOException {
return this.parseNotify(request, Transaction.class);
}
@Override
public RefundNotification parseRefundNotify(HttpServletRequest request) throws IOException {
return this.parseNotify(request, RefundNotification.class);
}
@Override
public void handleRefundSuccess(RefundNotification refundNotification) {
String outRefundNo = refundNotification.getOutRefundNo();
// 处理退款成功
}
}
控制器
java
package com.demo.controller;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import com.wechat.pay.java.service.refund.model.Status;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/wechatPay")
public class WechatPayController {
@Autowired
private PayService weixinPayService;
@PostMapping("/payNotify")
public Map<String, String> payNotify(HttpServletRequest request) throws IOException {
Transaction transaction = weixinPayService.parseTransactionNotify(request);
log.debug("transaction: {}", transaction);
// 成功了再修改订单状态
if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
weixinPayService.handlePaySuccess(transaction);
}
// 应答返回
Map<String, String> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "成功");
return map;
}
@PostMapping("/refundNotify")
public Map<String, String> refundNotify(HttpServletRequest request) throws IOException {
RefundNotification refundNotification = weixinPayService.parseRefundNotify(request);
log.debug("refundNotification: {}", refundNotification);
// 成功了再修改订单状态
if (refundNotification.getRefundStatus() == Status.SUCCESS) {
weixinPayService.handleRefundSuccess(refundNotification);
}
// 应答返回
Map<String, String> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "成功");
return map;
}
/**
* 支付
* @param orderPayForm
* @return
*/
@PostMapping("/prePay")
public PrepayVO pay(@RequestBody @Validated OrderPayForm orderPayForm) {
return weixinPayService.wechatPrePay(orderPayForm.getOrderId());
}
/**
* 退款
* @param orderRefundForm
*/
@PostMapping("/refund")
public void refund(@RequestBody @Validated OrderRefundForm orderRefundForm) {
weixinPayService.refundOrderMoney(orderRefundForm);
}
}
表单数据
java
package com.demo.form;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import javax.validation.constraints.NotNull;
@ApiModel("支付订单")
@Data
public class OrderPayForm {
@NotNull
private Long orderId;
}
java
package com.demo.form;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@ApiModel("订单退款")
@Data
public class OrderRefundForm {
@NotNull
private Long orderId;
@NotBlank
private String reason;
}
小程序支付
1、服务端返回支付所需参数
公共的配置参数
java
public Config getConfig() {
return new RSAConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.wechatPayCertificatesFromPath(wxPayConfig.getWechatPayCertificatePath())
.build();
}
java
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
JsapiServiceExtension service = new JsapiServiceExtension
.Builder()
.config(getConfig())
.build();
// 设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
// 金额
Amount amount = new Amount();
amount.setTotal(100);
amount.setCurrency("CNY");
request.setAmount(amount);
// 支付人
Payer payer = new Payer();
payer.setOpenid("OpenId");
request.setPayer(payer);
// 商户信息
request.setDescription("商品名称");
request.setAppid(wxMaConfig.getAppId());
request.setMchid(wxPayConfig.getMerchantId());
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
request.setOutTradeNo("OutTradeNo");
// 调用下单方法,得到应答
// response包含了调起支付所需的所有参数,可直接用于前端调起支付
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
log.debug("{}", response);
2、小程序端拉起支付
js
await wx.requestPayment({
timeStamp: '',
nonceStr: '',
package: '',
signType: '',
paySign: '',
})