赞
踩
注意: 个人无法实现微信支付,需是商家或企业
微信文档写的比较乱:
看文档顺序:
1.开发指引
2.JSAPI下单 (因为小程序调用的是JSAPI支付类型)
3.接口规则
左边下拉框的 证书/密钥/签名介绍 可以看一下
一篇特别好的文章 : 公钥,私钥和数字签名这样最好理解
5.SDK
这里使用的是这个 wechatpay-apache-httpclient 工具包
使用它构造HttpClient。得到的HttpClient在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。且此SDK还能定时更新平台证书
下面maven导入的也是其依赖包
6.查询订单
7.小程序调起支付API
8.支付通知
小程序微信支付流程图
由上图可知需写三个API
前端请求下单支付,后端返回支付参数
详情见: JSAPI下单
通过微信支付订单号或商户订单号获取其支付数据
详情见: 查询订单API
微信支付商户号:注册公众号时有的,唯一的
微信支付订单号:微信自动生成的订单号
商户订单号:自己生成的订单号
注: yaml文件里配置的 notify-url 为自己写的支付通知API的 @PostMapping 里的地址
详情见: 小程序调起支付API
签名和各类数据的加密解密是微信支付的难点
用到的依赖
<!--微信支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.2</version>
</dependency>
<!--Hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
配置文件 .yaml
多加如下配置
wechat-payment: #微信支付商户号 merchant-id: "xxxxxx" #微信支付证书序列号 cert-serial-number: "xxxxxx" #微信支付API V3秘钥 api-v3-key: "xxxxxx" #微信支付小程序APP ID app-id: "xxxxxx" #我的证书放在resources下wechat-payment-cert文件夹里 #微信支付公钥证书位置 public-cert-location: "/wechat-payment-cert/apiclient_cert.pem" #微信支付私钥证书位置 private-key-location: "/wechat-payment-cert/apiclient_key.pem" #支付回调地址 notify-url: "http://127.0.0.1/wechat-payment/paymentNotify"
调用到的工具类:
用来对签名和数据进行加密解密
import org.apache.commons.codec.binary.Base64; import org.springframework.util.Base64Utils; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.*; public class SignUtil { //编码方式 private static final String ENCODING = "UTF-8"; //加密方式 private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; //加密 //调用顺序:先加密后编码:先 sign256 后 encodeBase64 public static byte[] sign256(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateKey); signature.update(data.getBytes(ENCODING)); return signature.sign(); } //验证签名正确与否 //调用顺序:先解码后验证:先decodeBase64后verify256 public static boolean verify256(String data, byte[] sign, PublicKey publicKey) { if (data == null || sign == null || publicKey == null) { return false; } try { Signature signCheck = Signature.getInstance(SIGNATURE_ALGORITHM); signCheck.initVerify(publicKey); signCheck.update(data.getBytes(StandardCharsets.UTF_8)); return signCheck.verify(sign); } catch (Exception e) { return false; } } //编码 public static String encodeBase64(byte[] bytes) { return new String(Base64.encodeBase64(bytes)); } //解码 public static byte[] decodeBase64(String data) { byte[] result = null; try { result = Base64.decodeBase64(data); } catch (Exception e) { return null; } return result; } /** * 解密请求回调 * 参考https://developers.weixin.qq.com/community/develop/article/doc/000eeac2ba4898a9fd6be8b175bc13 * * @param apiV3Key 微信API V3 秘钥 * @param associatedData 附加数据 * @param nonce 随机串 * @param ciphertext 数据密文 * @return 解密后的数据密文 */ public static String decryptWechatPaymentResponseBody(String apiV3Key, String associatedData, String nonce, String ciphertext) { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES"); GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8)); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8)); byte[] bytes; try { bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext)); } catch (GeneralSecurityException e) { throw new IllegalArgumentException(e); } return new String(bytes, StandardCharsets.UTF_8); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } }
用到的实体类
@Data @AllArgsConstructor @NoArgsConstructor public class WeChatPaymentOrder { /** * 商品描述 string[1,127] */ String description; /** * 商户订单号 string[6,32] */ String outTradeNumber; /** * 商户订单号 string[6,32] * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 */ String notifyUrl; /** * 总金额,单位为分 */ Integer totalAmount; /** * 支付者 */ String userOpenId; }
@Data @AllArgsConstructor @NoArgsConstructor public class WechatPaymentData { /** * 时间戳 * 标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 */ String timeStamp; /** * 随机字符串 */ String nonceStr; /** * 订单详情扩展字符串 * 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** */ String orderPackage; /** * 签名方式 * 签名类型,默认为RSA,仅支持RSA。 */ String signType; /** * 签名 * 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 */ String paySign; }
controller层
@Slf4j @CrossOrigin @RestController public class WechatPaymentController { @Resource private WechatPaymentService wechatPaymentService; @Resource private WechatUserService wechatUserService; /** * 1.获取支付信息 * * @param loginCode 登录Code(与userOpenId二选一) * @param userOpenId 用户的微信OpenId (与loginCode二选一) * @param description 商品描述 * @param totalAmount 总价 * @return 支付数据(返回支付参数) */ @PostMapping("/wechat-payment/getWechatPaymentData") public WechatPaymentData getWechatPaymentData(String loginCode, String userOpenId, @RequestParam String description, @RequestParam Integer totalAmount) { //验证描述长度 int descriptionLength = description.length(); if (descriptionLength < 1 || descriptionLength > 127) { log.warn("description字段长度限制为[1,126]"); return null; } //如果是loginCode的话就需要转化为OpenId if (loginCode != null) { userOpenId = wechatUserService.getUserWechatOpenId(loginCode); } //如果为空则不继续执行了 if (userOpenId == null) { log.warn("无法获取用户标识符"); return null; } else { log.info("已获取用户OpenId:" + userOpenId); } return wechatPaymentService.getWechatPaymentData(userOpenId, description, totalAmount); } /** * 2.查询支付记录 * * @param transactionId 微信支付订单号(与商户订单号二选一) * @param outTradeNumber 商户订单号(与微信支付订单号二选一) * @return 支付数据 */ @GetMapping("/wechat-payment/getTransaction") public JSONObject getTransaction(String transactionId, String outTradeNumber) { if (transactionId == null && outTradeNumber == null) { log.warn("参数错误"); return null; } return wechatPaymentService.getTransaction(transactionId, outTradeNumber); } /** * 3.微信支付回调 * * @param body 微信提供的支付回调数据 */ @PostMapping("/wechat-payment/paymentNotify") public void paymentNotify( @RequestHeader Map<String, String> headers, @RequestBody String body) { //验证签名 boolean signResult = wechatPaymentService.verifyHttpSign(headers, body); if (!signResult) { log.warn("签名验证失败"); return; } //数据处理 JSONObject notifyObj = JSON.parseObject(body); wechatPaymentService.paymentNotify(notifyObj); //通过body数据判断是否支付成功 //将paymentNotify方法改为JSONObject返回值,返回数据处理(解密)过后的notifyObj //做支付成功判断并插入数据库 // ... } }
service层:
@Slf4j @Service public class WechatPaymentService { /** * 微信支付商户号 */ @Value("${wechat-payment.merchant-id}") String paymentMerchantId; /** * 微信支付API V3秘钥 */ @Value("${wechat-payment.api-v3-key}") String paymentApiV3Key; /** * 微信支付小程序APP ID */ @Value("${wechat-payment.app-id}") String paymentAppId; /** * 支付回调地址 */ @Value("${wechat-payment.notify-url}") String paymentNotifyUrl; /** * 验证器 */ @Resource Verifier merchantVerifier; /** * 私钥 */ @Resource PrivateKey merchantPrivateKey; /** * Http客户端 */ @Resource CloseableHttpClient httpClient; /** * 验证签名请求头 * * @param headers HTTP请求头 * @param responseBody 应答主体 * @return 是否通过验证 */ public Boolean verifyHttpSign(Map<String, String> headers, String responseBody) { /* * 注意,所有的HTTP请求的header都是小写字母 * 微信文档中给的不对 * */ log.info("获取到的平台Header"); log.info(String.valueOf(headers)); //获取可用的微信平台证书 X509Certificate wechatCertificate = merchantVerifier.getValidCertificate(); //获取微信平台证书的序列号(16进制转换) String wechatCertificateSerialNumber = wechatCertificate.getSerialNumber().toString(16); //获取传递的证书序列号 String certSerial = headers.get("wechatpay-serial"); //验证证书序列号(忽略大小写) if (!certSerial.equalsIgnoreCase(wechatCertificateSerialNumber)) { log.error("证书序列号不一致"); return false; } //应答时间戳(这里一定要写) String timeStamp = headers.get("wechatpay-timestamp"); //应答随机串 String nonce = headers.get("wechatpay-nonce"); //构建应答的验签名串 String signText = timeStamp + "\n" + nonce + "\n" + responseBody + "\n"; log.info("应答的验签名串"); log.info(signText); //微信支付的应答签名(BASE64加密后) String base64Signature = headers.get("wechatpay-signature"); //获取签名 byte[] sign = SignUtil.decodeBase64(base64Signature); //验证 return SignUtil.verify256(signText, sign, wechatCertificate.getPublicKey()); } /** * 查询支付记录 * * @param transactionId 微信支付订单号 * @param outTradeNumber 商户订单号 * @return 支付记录 */ public JSONObject getTransaction(String transactionId, String outTradeNumber) { String url; if (transactionId != null) { //微信支付订单号模式 url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/transaction_id?mchid=merchantId"; url = url.replace("transaction_id", transactionId); //微信支付商户号 url = url.replace("merchantId", paymentMerchantId); } else if (outTradeNumber != null) { //商户订单号模式 url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/out_trade_no?mchid=merchantId"; url = url.replace("out_trade_no", outTradeNumber); url = url.replace("merchantId", paymentMerchantId); } else { log.error("错误的数据请求"); return null; } //组装请求参数 HttpGet httpGet = new HttpGet(url); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); //发送数据 CloseableHttpResponse response = null; try { response = httpClient.execute(httpGet); } catch (IOException e) { e.printStackTrace(); } //解析数据 try { if (response != null) { String bodyAsString = EntityUtils.toString(response.getEntity()); return JSON.parseObject(bodyAsString); } else { log.error("数据为空"); return null; } } catch (Exception e) { e.printStackTrace(); return null; } } /** * 处理支付回调 * * @param jsonObject 数据 */ public void paymentNotify(JSONObject jsonObject) { //获取通知数据 JSONObject resource = jsonObject.getJSONObject("resource"); //附加数据 String associatedData = resource.getString("associated_data"); //随机串 String nonce = resource.getString("nonce"); //数据密文 String ciphertext = resource.getString("ciphertext"); //获得解密数据 String decryptString = SignUtil.decryptWechatPaymentResponseBody(paymentApiV3Key, associatedData, nonce, ciphertext); //解析数据 JSONObject decryptData = JSONObject.parseObject(decryptString); log.info("获得解析数据:"); log.info(String.valueOf(decryptData)); } /** * 获取微信支付数据 * * @param userOpenId 用户微信OpenId * @param description 描述 * @param totalAmount 总价 * @return 支付数据(返回支付参数) */ public WechatPaymentData getWechatPaymentData(String userOpenId, String description, Integer totalAmount) { //生成订单号 String outTradeNumber = IdUtil.simpleUUID(); //组装订单数据:商品描述、商户订单号、商户订单号、支付金额、支付者 WeChatPaymentOrder paymentOrder = new WeChatPaymentOrder( description, outTradeNumber, paymentNotifyUrl, totalAmount, userOpenId ); //获取PrePayId String prePayID = generatePrePayId(paymentOrder); if (prePayID == null) { log.error("无法获取PrePayID"); return null; } //获取当前时间戳(微信要求10位) String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); //订单详情扩展字符串 String orderPackage = "prepay_id=" + prePayID; //签名方式 String signType = "RSA"; //待签名字符串 String paySignText = paymentAppId + "\n" + timeStamp + "\n" + outTradeNumber + "\n" + orderPackage + "\n"; log.info("待签名字符串:"); log.info(paySignText); //签名结果 String signResult; try { //获取加密后的签名/对签名进行加密 byte[] sign256 = SignUtil.sign256(paySignText, merchantPrivateKey); signResult = SignUtil.encodeBase64(sign256); } catch (Exception e) { e.printStackTrace(); return null; } log.info("签名结果:"); log.info(signResult); return new WechatPaymentData( timeStamp, outTradeNumber, orderPackage, signType, signResult ); } /** * 生成PrePayId * * @param data 支付数据 * @return 获取PrePayId */ private String generatePrePayId(WeChatPaymentOrder data) { try { //组装数据 JSONObject rootNode = new JSONObject(); //应用ID rootNode.put("appid", paymentAppId); //直连商户号 rootNode.put("mchid", paymentMerchantId); //商品描述 string[1,127] rootNode.put("description", data.getDescription()); //商户订单号 string[6,32] //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 rootNode.put("out_trade_no", data.getOutTradeNumber()); //通知地址 rootNode.put("notify_url", data.getNotifyUrl()); //订单金额 JSONObject amountObject = new JSONObject(); amountObject.put("total", data.getTotalAmount()); rootNode.put("amount", amountObject); //支付者 JSONObject payerObject = new JSONObject(); payerObject.put("openid", data.getUserOpenId()); rootNode.put("payer", payerObject); //组装请求参数 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"); httpPost.setEntity(new StringEntity(rootNode.toString(), "UTF-8")); //发送数据 CloseableHttpResponse response = httpClient.execute(httpPost); //解析数据 String bodyAsString = EntityUtils.toString(response.getEntity()); JSONObject jsonObject = JSON.parseObject(bodyAsString); String prePayId = jsonObject.getString("prepay_id"); if (prePayId != null) { log.info("已生成PrePayId:" + prePayId); return prePayId; } else { log.error("无法获取PrePayId,错误信息:" + bodyAsString); return null; } } catch (Exception exception) { exception.printStackTrace(); return null; } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。