微信Native支付V3版本
微信支付在开发之前也是需要进行商户接入的
接入文档链接: https://pay.weixin.qq.com/index.php/core/home/login
Native支付介绍
商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式
对应的链接: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_3.shtml
微信提供的文档听详细的,需要准备的东西也有介绍,简单分为以下几种:
- AppId
- 商户号(MchId)
- 商户私钥
- 证书序列号
- APIV3秘钥
- 请求地址
当企业进行商户接入完毕后,就可以得到以上的参数,具体的步骤可以查看上面的文档
第一步
导入依赖
<!-- 微信支付 -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.2</version>
</dependency>
第二步
将下面的参数写在配置文件中,或者数据库中,看自己需求
- AppId
- 商户号(MchId)
- 商户私钥
- 证书序列号
- APIV3秘钥
- 请求地址
第三步
controller层写自己的需要接受前端给传过来的的参数,根据自己业务整
第四步
就是具体与微信进行交互了,建议抽取成工具类,以便复用
上代码
JSONObject resultObject = new JSONObject();
String zhxsu = EncryptUtil.Base64Decode(boat.getZhxsu());
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream("微信私钥".getBytes("utf-8")));
//使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials("微信商户id", new PrivateKeySigner("微信证书序列号", merchantPrivateKey)),
"微信APIV3密钥".getBytes("utf-8"));
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant("微信商户id", "微信证书序列号", merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
HttpPost httpPost = new HttpPost("微信请求地址");
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("mchid", "微信商户id(mchId)")
.put("appid", "微信appId")
.put("description", "商品描述")
.put("notify_url", "微信回调地址")
.put("out_trade_no", "自己生成的订单号")
.put("attach","其他,可以加可不加")
.put("time_expire", "过期时间");
rootNode.putObject("amount")
.put("total","金额");
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
assertTrue(response.getStatusLine().getStatusCode() != 401);
assertTrue(response.getStatusLine().getStatusCode() != 400);
HttpEntity entity2 = response.getEntity();
EntityUtils.consume(entity2);
JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
resultObject.put("code", 0);
resultObject.put("data", jsonObject.getString("code_url")); // code_url就是返回的二维码链接
resultObject.put("message", "成功");
return resultObject;
} catch (Exception e) {
e.printStackTrace();
log.error("调起微信支付出现问题=>{}", e.getMessage());
resultObject.put("code", 500);
resultObject.put("message", "微信支付请求失败");
return resultObject;
} finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
log.error("微信支付关闭资源报错==>{}", e.getMessage());
}
}
做到这一步的时候就可以的到一个二维码链接了
可以把二维码链接给前端,前端生成二维码展示给用户,用户扫描二维码支付成功后,微信会向规定好的回调地址发请求
通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
第五步
回调地址开发
controller
@CrossOrigin
@ResponseBody
@RequestMapping("你的微信回调地址")
public Map weCall(HttpServletRequest request) {
// 调用支付回调
return 你的微信回调业务类.你的微信回调业务方法(request);
}
service
由于代码不少,为了看起来不是那么复杂,我分了三个方法
/**
* 微信回调处理
*
* @return
*/
@Override
public Map weCallback(HttpServletRequest request) {
Map<String, String> resultMap = new HashMap<String, String>();
try {
StringBuilder sigStr = new StringBuilder();
sigStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
sigStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
BufferedReader br = request.getReader();
String str = null;
StringBuilder builder = new StringBuilder();
while ((str = br.readLine()) != null) {
builder.append(str);
}
sigStr.append(builder.toString()).append("\n");
// 验证签名
if (!signVerify(request.getHeader("Wechatpay-Serial"), sigStr.toString(), request.getHeader("Wechatpay-Signature"))) {
log.error("微信回调签名错误");
resultMap.put("code", "FAIL");
resultMap.put("message", "微信回调签名错误");
return resultMap;
}
// 解密密文
String resultWeChat = decryptOrder(builder.toString());
if (StringUtils.isBlank(resultWeChat)) {
log.error("微信回调解密失败");
resultMap.put("code", "FAIL");
resultMap.put("message", "微信回调解密失败");
return resultMap;
}
JSONObject jsonObject = JSONObject.parseObject(resultWeChat);
// 验证订单
if ("SUCCESS".equals(jsonObject.getString("trade_state"))) {
// 拿到自己的订单详情了,可以根据自己的实际业务进操作了
String outTradeNo = jsonObject.getString("out_trade_no"); // 订单号
String attach = jsonObject.getString("attach"); // 附加数据
int intValue = JSONObject.parseObject(jsonObject.getString("amount")).getIntValue("payer_total");
// 充值金额 微信是以 分 作为计量单位
String eupj = transition(intValue);
// 支付完成时间 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
String tepme = DateUtils.weChatDateDispose(jsonObject.getString("success_time")); // 支付完成时间
String transactionId = jsonObject.getString("transaction_id"); // 微信流水号
resultMap.put("code", "SUCCESS");
resultMap.put("message", "成功");
return resultMap; // 请不要修改或删除
// todo 同步成功短信通知
} else {
String outTradeNo = jsonObject.getString("out_trade_no"); // 订单号
log.error("微信回调支付状态不是成功,订单号==>{},订单状态是={}", outTradeNo, jsonObject.getString("trade_state"));
resultMap.put("code", "FAIL");
resultMap.put("message", "同步失败");
return resultMap;
}
} catch (IOException e) {
e.printStackTrace();
log.error("微信支付回调出现错误==>{}", e.getMessage());
resultMap.put("code", "FAIL");
resultMap.put("message", e.getMessage());
return resultMap;
}
}
/**
* 微信支付回调签名验证
*/
private boolean signVerify(String serial, String message, String signature) {
AutoUpdateCertificatesVerifier verifier = null;
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream("微信支付私钥".getBytes("utf-8")));
// 使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials("商户id(mch_id)", new PrivateKeySigner("证书序列号", merchantPrivateKey)),
"API V3秘钥".getBytes("utf-8"));
return verifier.verify(serial, message.getBytes("utf-8"), signature);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.error("微信支付回调签名验证出现异常=>{}", e.getMessage());
}
return false;
}
/**
* 微信支付回调解密
*/
private String decryptOrder(String body) {
try {
AesUtil util = new AesUtil("API V3秘钥".getBytes("utf-8"));
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(body);
JsonNode resource = node.get("resource");
String ciphertext = resource.get("ciphertext").textValue();
String associatedData = resource.get("associated_data").textValue();
String nonce = resource.get("nonce").textValue();
return util.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
} catch (Exception e) {
e.printStackTrace();
log.error("微信支付回调解密出现问题==>{}", e.getMessage());
}
return null;
}
/**
* 分转元 保留小数点后两位
*/
private String transition(int num) {
BigDecimal bigDecimal1 = new BigDecimal(num + "");
BigDecimal bigDecima12 = new BigDecimal("100.00");
return bigDecimal1.divide(bigDecima12).setScale(2).toString();
}
以上就是发起请求和完成支付的步骤
注意点
- 里面返回的格式和"code"中的内容千万不要改变,因为微信就是根据这个进行查看回调结果的,如果微信在24小时内没有收到回调信息的话,会自动退款的
- 给微信发送请求生成订单二维码链接时,金额一定要是int类型,而且还是以分为单位,我在下面附上 分转元, 元转分的方法
- 使用微信V3支付的话可能会报java.security.InvalidKeyException: Illegal key size这个错误,
原因:JDK 默认的 Key 长度不支持 256,使用的 AESUtil 是需要256的
方案:
去 oracle 官网下载对应jdk版本的 jce_policy ,JDK8 点此下载 http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html,不想注册Oracle,嫌麻烦的,来Q群183579482 的群文件里面下载吧。
下载之后找到你电脑安装的路径 方法也适用Centos),将下载出来的两个jar包解压到里面把之前的jar包覆盖了,就OK;
该解决方案来自于: https://blog.youkuaiyun.com/qq_41647999/article/details/109721250
完结,撒花