微信支付有多种模式,这里阐述的是服务商模式下的Native支付,适用场景为用户在服务商或子商户(也叫特约商户)前端选择微信支付后,服务商调起支付并接收回调的全过程。
特别提醒:本代码只有后端请求,服务商模式和商户模式有所不同,请根据自身需求自行选择。
框架:SpringBoot \ jdk1.8
一、所需实体类
@Data
public class Amount {
private Integer total;
private String currency;
// Getters and Setters
}
@Data
public class WechatPayNotifyRequest {
@JsonProperty("id")
private String id;
@JsonProperty("create_time")
private Date createTime;
@JsonProperty("resource_type")
private String resourceType;
@JsonProperty("event_type")
private String eventType;
@JsonProperty("summary")
private String summary;
@JsonProperty("resource")
private Resource resource;
// Getters and Setters
}
@Data
public class TransactionInfo {
@JsonProperty("transaction_id")
private String transactionId;
private Amount amount;
@JsonProperty("sub_mchid")
private String subMchId;
@JsonProperty("trade_state")
private String tradeState;
@JsonProperty("bank_type")
private String bankType;
@JsonProperty("promotion_detail")
private List<PromotionDetail> promotionDetail;
@JsonProperty("success_time")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
private Date successTime;
private Payer payer;
@JsonProperty("out_trade_no")
private String outTradeNo;
private String appid;
@JsonProperty("trade_state_desc")
private String tradeStateDesc;
@JsonProperty("trade_type")
private String tradeType;
private String attach;
@JsonProperty("scene_info")
private SceneInfo sceneInfo;
@Data
public static class Amount {
@JsonProperty("payer_total")
private Integer payerTotal;
private Integer total;
private String currency;
@JsonProperty("payer_currency")
private String payerCurrency;
}
@Data
public static class PromotionDetail {
private Integer amount;
@JsonProperty("wechatpay_contribute")
private Integer wechatpayContribute;
@JsonProperty("coupon_id")
private String couponId;
private String scope;
@JsonProperty("merchant_contribute")
private Integer merchantContribute;
private String name;
@JsonProperty("other_contribute")
private Integer otherContribute;
private String currency;
@JsonProperty("stock_id")
private String stockId;
@JsonProperty("goods_detail")
private List<GoodsDetail> goodsDetail;
}
@Data
public static class GoodsDetail {
@JsonProperty("goods_remark")
private String goodsRemark;
private Integer quantity;
@JsonProperty("discount_amount")
private Integer discountAmount;
@JsonProperty("goods_id")
private String goodsId;
@JsonProperty("unit_price")
private Integer unitPrice;
}
@Data
public static class Payer {
private String openid;
}
@Data
public static class SceneInfo {
@JsonProperty("device_id")
private String deviceId;
}
}
二、Native下单
1.微信请求签名头生成方法
/**
* 微信支付 V3 签名工具类
*/
public class WechatPaySignUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
// 禁用缩进输出,确保紧凑格式
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
// 可选:忽略 null 字段(根据业务需求决定是否启用)
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
/**
* 加载商户私钥(PKCS#8 格式)
*
* @param privateKeyPEMContent PEM 格式的私钥内容
* @return PrivateKey
* @throws Exception
*/
public static PrivateKey loadPrivateKey(String privateKeyPEMContent) throws Exception {
String pem = privateKeyPEMContent
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] pkcs8Bytes = Base64.getDecoder().decode(pem);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
/**
* 生成请求所需的 Authorization 头
*
* @param method HTTP 方法,如 POST/GET
* @param urlPath 请求路径,如 /v3/pay/transactions/native
* @param body 请求体 JSON 字符串(POST 请求),GET 请求传 ""
* @param merchantId 商户号 mchid
* @param serialNo 证书序列号
* @param privateKey 商户私钥
* @return 完整的 Authorization Header
* @throws Exception
*/
public static String generateAuthorizationHeader(
String method,
String urlPath,
String body,
String merchantId,
String serialNo,
PrivateKey privateKey) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
String message = buildMessage(method, urlPath, timestamp, nonceStr, body);
String signature = sign(message, privateKey);
String format = String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%s\",serial_no=\"%s\",signature=\"%s\"",
merchantId, nonceStr, timestamp, serialNo, signature);
System.out.println(format);
return format;
}
/**
* 构建签名原文
*/
private static String buildMessage(String method, String urlPath, String timestamp, String nonceStr, String body) {
return method.toUpperCase() + "\n" +
urlPath + "\n" +
timestamp + "\n" +
nonceStr + "\n" +
body + "\n";
}
/**
* 使用私钥对消息进行签名
*/
private static String sign(String message, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(message.getBytes(StandardCharsets.UTF_8));
byte[] signedBytes = signature.sign();
return Base64.getEncoder().encodeToString(signedBytes);
}
/**
* 将对象转换为 JSON 字符串
*/
public static String toJson(Object obj) throws IOException {
return objectMapper.writeValueAsString(obj);
}
}
2.Native下单接口
@Service
public class WeChatPayGet {
public Map<String, Object> submitNativeOrder() {
try {
// 1. 构造请求参数对象
WechatPayNativeRequest request = new WechatPayNativeRequest();
request.setSp_appid("appID");
request.setSp_mchid("服务商商户号");
request.setSub_mchid("商户商户号");
request.setDescription("订单简介");
request.setOut_trade_no("订单唯一编号(系统内唯一)");
request.setNotify_url("回调接口接收地址");
//获取收费金额
Amount amount = getAmount();
request.setAmount(amount);
// 2. 序列化为 JSON 字符串
String requestBody = WechatPaySignUtil.toJson(request);
// 3. 加载商户私钥
String privateKeyPEM = getKeyLoader();
PrivateKey privateKey = WechatPaySignUtil.loadPrivateKey(privateKeyPEM);
// 4. 生成签名头
String authorization = WechatPaySignUtil.generateAuthorizationHeader(
"POST",
"/v3/pay/partner/transactions/native",
requestBody,
"商户号",
"证书编号",
privateKey);
// 5. 发送请求
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", authorization);
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(
"https://api.mch.weixin.qq.com/v3/pay/partner/transactions/native",
entity,
String.class
);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(response.getBody(), Map.class);
Map<String, Object> result = new HashMap<>();
return result;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取微信支付出错,请联系系统管理员");
}
}
private @NotNull Amount getAmount() {
Amount amount = new Amount();
amount.setTotal("订单金额");
amount.setCurrency("CNY");
return amount;
}
private static String getKeyLoader() {
try {
return loadKey("私钥地址");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
三、微信支付回调请求接收
1.请求接收接口
public class WechatPayNotifyController {
/**
* 微信支付回调
*/
@Autowired
private ObjectMapper objectMapper;
@Autowired
private WeChatPayResult weChatPayResult;
private Certificate wechatCertificate;
@PostConstruct
public void init() throws Exception {
this.wechatCertificate = loadCertificate();
}
@PostMapping("/notify")
public String handleWechatPayNotify(@RequestBody String requestBody, HttpServletRequest request) {
System.out.println("微信支付回调成功");
try {
// 1. 验证签名
validateWechatSign(request, requestBody);
System.out.println("验签成功");
// 2. 反序列化通知内容
WechatPayNotifyRequest notifyRequest = objectMapper.readValue(requestBody, WechatPayNotifyRequest.class);
System.out.println("反序列化成功");
// 3. 获取商户私钥
String privateKey = getKeyLoader();
String cleanedKey = privateKey.replace("\uFEFF", "").trim();
System.out.println("私钥已获取");
// 4. 解密 resource 数据
AesUtil aesUtil = new AesUtil(cleanedKey.getBytes(StandardCharsets.UTF_8));
String decryptData = aesUtil.decryptToString(
notifyRequest.getResource().getAssociatedData().getBytes(StandardCharsets.UTF_8),
notifyRequest.getResource().getNonce().getBytes(StandardCharsets.UTF_8),
notifyRequest.getResource().getCiphertext()
);
System.out.println("数据已解密");
// 5. 转换为交易信息
TransactionInfo transactionInfo = objectMapper.readValue(decryptData, TransactionInfo.class);
System.out.println("交易信息转换成功,进行业务逻辑判断");
// 6. 判断是否已处理过该订单(幂等性)
boolean check = "自己的检测方法";
if (check) {
// 7. 没处理过,执行业务逻辑
weChatPayResult.controllerAll(transactionInfo);
}else {
System.out.println("系统内已有该订单");
}
// 8. 返回成功响应
return buildSuccessResponse();
} catch (JsonProcessingException e) {
return buildFailResponse("JSON解析失败");
} catch (Exception e) {
return buildFailResponse("系统内部错误");
}
}
private void validateWechatSign(HttpServletRequest request, String body) throws Exception {
String signature = request.getHeader("Wechatpay-Signature");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String message = timestamp + "\n" + nonce + "\n" + body + "\n";
Signature verifier = Signature.getInstance("SHA256WithRSA");
verifier.initVerify(wechatCertificate);
verifier.update(message.getBytes(StandardCharsets.UTF_8));
}
private Certificate loadCertificate() throws Exception {
String pemData = getPublicLoader();
StringReader reader = new StringReader(pemData.trim());
PemReader pemReader = new PemReader(reader);
PemObject pemObject = pemReader.readPemObject();
pemReader.close();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return certificateFactory.generateCertificate(new ByteArrayInputStream(pemObject.getContent()));
}
private String buildSuccessResponse() {
return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
}
private String buildFailResponse(String reason) {
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[" + reason + "]]></return_msg></xml>";
}
private static String getKeyLoader() {
try {
return loadKey("公钥地址");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String getPublicLoader() {
try {
return loadKey("证书地址");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}