Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
一、准备工作
微信商户平台
1、获取商户号merchantId
账户中心->商户信息->微信支付商户号
2、获取商户API证书及密钥
参照文档:
什么是商户API证书?如何获取商户API证书?腾讯客服成立于2002年,承接了腾讯集团的所有业务的海量用户服务,一直以来致力于依托先进的互联网技术,致力于通过建设多元化特色渠道,智能化解决用户问题,同时传递用户心声,助力产品优化体验,成为业界领先的互联网客服团队。https://kf.qq.com/faq/161222NneAJf161222U7fARv.html得到三个文件apiclient_cert.p12,apiclient_cert.pem,apiclient_key.pem
3、获取商户证书序列号merchantSerialNumber
账户中心->API安全->API证书管理
4、设置APIV3密钥
参照文档:
5、获取appID
产品中心->AppID账号管理
6、下载CertificateDownloader
Certificate Downloader 是 Java 微信支付 APIv3 平台证书的命令行下载工具。该工具可从 https://api.mch.weixin.qq.com/v3/certificates 接口获取商户可用证书,并使用 APIv3 密钥和AES_256_GCM 算法进行解密,并把解密后证书下载到指定位置。
完整命令:
java -jar CertificateDownloader.jar -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath} -c ${wechatpayCertificateFilePath}
必需参数有:
- 商户的私钥文件,即 -f
- 证书解密的密钥,即 -k
- 商户号,即 -m
- 保存证书的路径,即 -o
- 商户证书的序列号,即 -s
非必需参数有:
- 微信支付证书,用于验签,即 -c
对于微信支付平台的应答,需要使用平台证书来进行验签;但平台证书只能通过获取平台证书接口下载,所以当第一次去获取证书时,会出现个“死循环”。
为解决这个“死循环”,可以临时跳过验签,来获得证书。也就是说可以不提供微信支付证书参数(-c 参数)来下载,
错误:如果命令出现“java.security.InvalidKeyException: Illegal key size”的错误,应该是jdk版本导致微信支付256位密钥加解密失败。
解决办法:
- 方法1:
下载最新版本的jdk1.8
- 方法2:
下载JCE无限制权限策略文件local_policy.jar
https://wwi.lanzoup.com/iXGs404zm1dg
解压后
替换jdk1.8.0_xxx/jre/lib/security/目录下的local_policy.jar
7、开通Native支付
产品中心->我的产品->Native支付
二、接入支付
1、引入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.6</version>
</dependency>
2、Native下单
接口文档:
微信支付-开发者文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtmlJava实现代码:
/** 商户号 */
private static String merchantId = "";
/** 商户API私钥路径 */
private static String privateKeyPath = "xxxx/apiclient_key.pem";
/** 商户证书序列号 */
private static String merchantSerialNumber = "";
/** 商户证书路径 */
private static String wechatPayCertificatePath = "xxxx/output.pem";
/** APPID */
private static String appID = "";
/** 支付成功回调地址 */
private static String notifyUrl = "";
private static NativePayService service;
/**
* 微信预支付
*/
public static String prepareWXPay(String title, float money, String outTradeNo) {
// 初始化商户配置
if (service == null) {
RSAConfig config =
new RSAConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.wechatPayCertificatesFromPath(wechatPayCertificatePath)
.build();
// 初始化服务
service = new NativePayService.Builder().config(config).build();
}
PrepayRequest request = new PrepayRequest();
request.setAppid(appID);
request.setMchid(merchantId);
request.setOutTradeNo(outTradeNo);
request.setDescription(title);
request.setNotifyUrl(notifyUrl);
Amount amount = new Amount();
amount.setTotal(Convert.toInt(money * 100));
amount.setCurrency("CNY");
request.setAmount(amount);
// 调用接口
PrepayResponse resp = service.prepay(request);
if (resp != null) {
return resp.getCodeUrl();
}
return null;
}
接口调用成功后会获得二维码的串,转成二维码后把二维码图片链接返给前端,或者直接把串丢给前端让前端自己生成二维码。
3、支付回调处理
/**
* 微信支付通知
*/
@RequestMapping(value = "/wxpayNotifyUrl", method = RequestMethod.POST)
@ResponseBody
public void wxPayNotify(@RequestBody JSONObject jsonObject) {
log.info("wxpayNotify:{}", jsonObject);
log.info("event_type:{}, summary:{}", jsonObject.getString("event_type"), jsonObject.getString("summary"));
if (jsonObject.getString("event_type").equals("TRANSACTION.SUCCESS")) {
JSONObject resource = jsonObject.getJSONObject("resource");
String ret = WXPayUtil.verifyPayNotify(resource);
log.info("ret:{}", ret);
if (ret != null) {
JSONObject retObj = JSON.parseObject(ret);
if (retObj.getString("trade_state").equals("SUCCESS")) {
log.info(retObj.getString("trade_state_desc"));
onWXPaySuccess(retObj.getString("out_trade_no"));
}
else {
log.info(retObj.getString("trade_state_desc"));
onWXPayFailure(retObj.getString("out_trade_no"));
}
}
}
}
支付回调验签
/**
* 支付回调验签
* @param resource 回调信息
*/
public static String verifyPayNotify(JSONObject resource) {
String associated_data = resource.getString("associated_data");
String cipherText = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String ret = null;
try {
String decryptData = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText);
System.out.println("decryptData = " + decryptData);
ret = decryptData;
//TODO 业务校验
} catch (Exception e) {
System.out.println("e = " + e);
}
return ret;
}