最近在接微信支付相关功能,支付结果回调折腾了比较久,微信官方文档里面只给了回调接口说明,包括:第一步验证签名,第二步参数解密,但是并未给具体实例,整体来说上手还是有点复杂,废话不多说,直接上代码;
前期准备:
微信支付,商家id,商家证书,证书SerialNo,apiKey, 这个是接入支付的准备,到回调这是,这些基本都已经ok了
依赖官方sdk:
<!--微信转账-->这个是支付的时候用到的sdk <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.14</version> </dependency> <!---回调通知签名校验以及参数解密相关---> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.9</version> </dependency>
支付回调的地址https://xxx/weixin/pay/notify,其中xxx是你服务器的域名,微信支付官方说明必须是https的
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class TransferConfig {
@Value("${transfer.merchantId}")
public String merchantId;
@Value("${transfer.marchant.keypath}")
public String privateKeyPath;
@Value("${transfer.merchantSerialNo}")
public String merchantSerialNumber;
@Value("${transfer.apiV3key}")
public String apiV3Key;
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hyhr.server.entity.TransferBatchInfo;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.java.core.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Locale;
@Slf4j
@RestController
@RequestMapping("/weixin/pay")
public class WxPayController {
@Autowired
private TransferConfig transferConfig;
@Autowired
private WxTransferService wxTransferService;
private CertificatesManager certificatesManager;
@PostConstruct
public void init() throws Exception {
//获取商户私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(transferConfig.privateKeyPath);
certificatesManager = CertificatesManager.getInstance();
//向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(transferConfig.merchantId, new WechatPay2Credentials(transferConfig.merchantId,
new PrivateKeySigner(transferConfig.merchantSerialNumber, merchantPrivateKey)),
transferConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
//从证书获取verifier
Verifier verifier = certificatesManager.getVerifier(transferConfig.merchantId);
}
@PostMapping("/notify")
public void callBack(HttpServletRequest request, HttpServletResponse response) {
String characterEncoding = request.getCharacterEncoding();
//从请求头获取验签字段
String timeStamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String certSn = request.getHeader("Wechatpay-Serial");
log.info("wx callback params,timesStamp:{},nonce:{},signature:{},certSn:{}", timeStamp, nonce, signature, certSn);
try(BufferedReader reader = new BufferedReader(new
InputStreamReader(request.getInputStream()))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
//检验签名
Verifier verifier = certificatesManager.getVerifier(transferConfig.merchantId);
String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
NotificationRequest notifyRequest = new NotificationRequest.Builder().withSerialNumber(sn)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, transferConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
//验证和解析请求体
Notification notification = handler.parse(notifyRequest);
log.info("wx callback response:{}", JSON.toJSONString(notification));
JSONObject res = JSON.parseObject(notification.getDecryptData());
String outBatchNo = res.getString("out_batch_no");
String batchStatus = res.getString("batch_status");
//处理相关业务
TransferBatchInfo transferBatchInfo = new TransferBatchInfo();
transferBatchInfo.setOutBatchNo(outBatchNo);
transferBatchInfo.setBatchStatus(batchStatus);
wxTransferService.notifyTransferBatchStatus(transferBatchInfo);
//响应微信服务器
response.setStatus(200);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "SUCCESS");
jsonObject.put("message", "SUCCESS");
response.getOutputStream().write(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
log.error("wx callback error", e);
response.setStatus(500);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "ERROR");
jsonObject.put("message", "签名失败");
try {
response.getOutputStream().write(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception ex) {
log.error("微信回调失败",e);
}
}
}
}
校验签名和参数解密,官方的NotificationHandler直接就给搞定了,大大简化了自己去校验签名和解密的麻烦
同时有一个坑,由于工程用的spring-boot版本比较就,里面使用jackson-databind的版本还是2.10的,和wechatpay-apache-httpclient里面使用的版本是2.13又冲突,我把spring-boot版本升级了,使使用jackson-databind版本在2.11之上就不会报错了
参考资料: