微信支付_V3版本
官方文档
需要下载wechatpay-apache-httpclient,很多具体的代码都在这个文档里。
需要简单了解的:
- 微信支付没有沙盒测试
- 在申请时,需要填写用户执照等信息。开发过程中需要对接 预支付接口和下单成功 两个接口。
- 微信支付平台证书是会发生变化的(5天)。
- 所有参数在官方文档中有介绍。
这些是英文和概念需要知道,且其中某些是需要去阿里云获取的:
- mchild(商户id)
- API key:主要用于平台证书解密、回调信息解密
- 商户API证书:是指由商户申请的,包含商户的商户号、公司名称、公钥信息的证书。
- 私钥:申请商户API证书时,会生成商户私钥
- 签名:私钥对API URL、消息体等关键数据的组合进行SHA-256 with RSA签名
本文的目的是 帮助读者梳理核心步骤,以至于在开发中不会显得脑瓜子很乱。
没有完完整整的开发步骤,没有完完整整的代码。
微信支付总结
总共有三个大步骤,每个大步骤中都有一个梳理,将大步骤分解为几个小步骤。
为帮助理解,梳理部分是按着代码倒叙方式写的。
可以先看梳理部分。
步骤1:
用户下单发起支付,商户可通过微信支付APP下单API创建支付订单。
商户调用APP下单API后,分正常返回和异常返回情况:
- 正常返回:返回prepay_id,商户可根据返回的prepay_id来生成调用OpenSDK的签名以执行下一步。
- 异常返回:返回http code或错误码,商户可根据http code列表 或错误码说明来排查原因并执行下一步操作
梳理:请结合下方获取预下单id
一起看。
- execute():
发起订单的关键语句是CloseableHttpResponse response = httpClient.execute(httpPost)
。
请求是不是要带一堆参数过去?参数在哪儿呢?参数都存储在了httpPost里。 - 参数设置和变换:
参数都用rootNode.put()来进行设置了,设置了之后rootNode转成bos,bos放进httpPost中。
rootNode —> bos —>htpPost。
这就能执行1.execute()
了。 - 执行完之后,要能收到一个prepay_id。这个id通过
String bodyAsString = EntityUtils.toString(response.getEntity())
拿到手。
其他函数,类等都是起辅助作用。
步骤2:
商户通过APP调起支付OpenSDK调起微信支付,发起支付请求,有关OpenSDK调起支付的详细说明,请参考2.2.2部分的说明
梳理:请结合代码App调起支付
一起看。
- App调起的关键代码是
api.sendReq(request)
。调起需要很多参数,参数在那儿呢?参数在 request中。 - request中有很多参数,最难的是签名。以下是签名的计算方法:
- 构造签名串
- 计算签名值:对签名串进行 SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。 签名的函为RsaCrytoUtil.encryOAEP();
步骤3:顺着看
当用户完成支付,微信会把相关支付结果通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答 。
梳理:拿到结果后的步骤梳理,结合回调和验签以及之后的内容
查看
-
取参数:微信会传给我们一个Json格式的数据,我们要将结果取出来
将JSON内容放入builder中,在代码中有提到,请结合查看
-
验签:为确保是微信官方发来的数据,需要对其数据进行验签
- 获取应答签名:通过request.getHeader(Wechatpay-Signatrue)得到微信发来数据中的签名
- 验证签名:构造一个签名和1中签名比对,比对成功则ok,不然可能是别人的攻击。过程在“验证签名”标题下的代码中
-
解密密文:有些数据是加密处理的,解密密文的作用是为了步骤4,代码见“解密密文标题”
-
验证其他参数:比如我这边的价格和微信发来数据中的价格是否一致等,这一块自己写
-
发回支付结果成功的消息:这一步是必须的,写一句
result.put("code","SECCESS")
,就ok。当然,最后需要将result返回
签名有两个:一个是发送时,需要给微信服务器的。一个是从服务器接收的消息,需要验证是微信服务器,而不是其他黑客分子的。
1. 获取预下单ID
-
配置maven依赖,可能需要下载阿里云镜像,pom中加入junit依赖(请自行配置)
-
下载wechatpay-apache-httpclient,将README.md中的下单代码( createOrder()函数 )复制,修改下单API的URL,修改其他参数。
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type","application/json; charset=utf-8"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode rootNode = objectMapper.createObjectNode(); // 参数设置都用rootNode.put()来进行设置了 rootNode.put("mchid","1900009191") .put("appid", "wxd678efh567hg6787") .put("description", "Image形象店-深圳腾大-QQ公仔") .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php") .put("out_trade_no", "1217752501201407033233368018"); rootNode.putObject("amount") .put("total", 1); rootNode.putObject("payer") .put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"); // rootNode转成bos objectMapper.writeValue(bos, rootNode); // bos放进httpPost中 httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); // execute(),参数都存储在了httpPost中 CloseableHttpResponse response = httpClient.execute(httpPost); // 执行完之后,要能收到一个prepay_id,你可以认为就是 bodyAsString String bodyAsString = EntityUtils.toString(response.getEntity()); System.out.println(bodyAsString);
-
将httpClient和verifier的代码粘贴过来。修改privateKey、mchId,mchId等(这一块可以先不看)
private CloseableHttpClient httpClient; private AutoUpdateCertificatesVerifier verifier; @Before public void setup() throws IOException { PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey( new ByteArrayInputStream(privateKey.getBytes("utf-8"))); //使用自动更新的签名验证器,不需要传入证书 verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8")); httpClient = WechatPayHttpClientBuilder.create() .withMerchant(mchId, mchSerialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)) .build(); }
-
try-catch
-
运行获得
prepay_id
(预支付)即成功
2. App调起支付
-
首先需要构造签名串,计算签名值
// 在System.out.println(bodyAsString)之后写 // 1. 构造签名串 String timestamp = System.currentTimeMillis()+""; // 时间戳 String nonce = RandomUtil.randomString(32); // 随机数,使用hutool的工具类 // 预支付交易会话ID Json node = objectMapper.readTree(bodyAsString); String presessionid = node.get("prepay_id"); // 应用id,时间戳,随机字符串,预支付交易会话ID,构造签名串 ...... // 拼接,执行此函数后签名串就构造好了 StringBuilder builder = new StringBuilder(); builder.append(APP_id).addpend("\n"); ....... ; // 2. 计算签名值 String ciphertext = RsaCryptoUtil.encrytOAEP(builder.toString, verifier.getValidCertificate()); // 加密
-
将接口参数放入map中返回给前端(这一块可以先不看)
Map map = new Map(); map.put("timestamp",timestamp); ..........
IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
api.sendReq(request); // sendReq()
3. 回调和验签(下面有总的callback)
梳理部分的大框架:
// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
//1. 验证签名的需要的参数
//result.getHeader("Wechatpay-Timestamp");
//result.getHeader("Wechatpay-Nonce");
//result.getHeader("Wechatpay-Signature");
//result.getHeader("Wechatpay-Serial");
// 将Json内容放入builder中
Map result = new Map();
resuit.put("code","FAILD");// 默认code失败
try{
BufferedReader br = request.getReader();
String str = null;
StringBuilder builder = new StringBuilder();
while( (str = br.readLine())!=null ){
builder.append(str);
}
// 2. 验证签名
// 3. 解密密文
// 4. 验证订单
// 5. 发回结果
result.put("code","SUCCESS");
} catch(IOException e){
e.printStackTrace();
}
return result;
}
3.1 验证签名
// serial:请求头中携带的序列号, 报文, 签名
public static boolean signVerify(String serial, String message, String signature){
// 获取 verifier
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
//使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
// 验证签名
try{
return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
}
catch(UnSupportedEncodingException e) {
e.printStackTrace();
}
return false;
}
3.2 解密密文
public static String decryptOrder(String body){
try{
AesUtil util = new AesUtil( PayConstants.API_V3KEY.getBytes("utf-8") );
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(body);
// 从node中拿到resource
JsonNode resource = node.get("resource");
// 拿数据密文
String ciphertext = resouce.get("ciphertext").textValue();// 还是Json类型,需要textValue()转String
String associatedData = resoure.get("ciphertext").textValue();
String nonce = resoure.get("nonce").textValue();
return util.decryToString( associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext );
} catch(UnsupportEncodingException e){
e.printStackTrace();
}
return null;
}
3.3 总的callback
中间步骤不是分开的,写一起吧
// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
// 1. 验证签名的需要的参数
// result.getHeader("Wechatpay-Timestamp");
// result.getHeader("Wechatpay-Nonce");
// result.getHeader("Wechatpay-Signature");
// result.getHeader("Wechatpay-Serial");
// 将Json内容放入builder中
Map result = new Map();
resuit.put("code","FAILD");// 默认code失败
try{
// 签名构造
StringBuilder signStr = new StringBuiler();
signStr.append( result.getHeader("Wechatpay-Timestamp").append("\n") ); // 时间戳
signStr.append( result.getHeader("Wechatpay-Nonce").append("\n") ); // 随机数
BufferedReader br = request.getReader();
String str = null;
StringBuilder builder = new StringBuilder();
while( (str = br.readLine())!=null ){
builder.append(str);
}
signStr.append( builder.toString().append("\n") ); // 报文主体
// 2. 验证签名
if( !signVerify(result.getHeader("Wechatpay-Serial"), signStr.toString, ), result.getHeader("Wechatpay-Signature") ) {
return result;
}
// 3. 解密密文
decryptOrder(builder.toString());
// 4. 验证订单
...................// 验证回调是否为微信官方发来的
// 5. 发回结果
result.put("code","SUCCESS");
} catch(IOException e){
e.printStackTrace();
}
return result;
}
本文仅供参数