当前位置:   article > 正文

微信支付总结(JSAPI)(V3)(JAVA)_wechatpay-java

wechatpay-java

        前一阵子做了一个微信支付相关的功能,期间走了不少的弯路。在这里给大家趟趟雷,希望大家能因此受益。在这里,我从头到尾一步步给大家捋顺。

目录

一 相关准备

 二 开始写代码

三 运行代码


一 相关准备

        实现微信支付首先得有对应的商户号。商户号是甲方的,但是现实中甲方对这些往往是两眼一抹黑,所以关于申请的工作往往是开发者弄。但是没关系,跟着腾讯提供的引导一步步走就行了。如果材料齐全的话,大约一到两天的时间就会下来。超管的角色默认和甲方提供的法人电话微信进行绑定,如果超管所绑定的微信用户电话号不是法人,则需要上传授权函,授权函的格式在帮助说明中有。

        等到商户号下来之后,有几个东西需要设置,这就和开发有关了。官方对此有比较明确的说明,地址为JSAPI支付-接入前准备 | 微信支付商户平台文档中心。我做的项目是jsapi支付类型,他的形式在腾讯支付文档中有详细的说明。主要是需要下面这几个:1 商户号:这个在后台里就能看到。2 appid:这个需要找甲方要。商户号和appid之间是多对多的关系。绑定商户号和appid需要在商户平台进行申请,然后在appid对应的公众平台后台进行同意。3 商户证书序列号。这个商户证书序列号在申请完证书之后就可以看到。在微信支付:API商户证书序列号serialNo获取-YES开发框架网这篇文章中可以看到如何获取,而且你需要有超级管理员权限。4 商户APIV3密钥。这个在文档中如何配置也有明确的说明。

        当你按照文档下载商户证书时,你会得到一个zip文件,解压,你会得到4个文件,一定要保存好。

 二 开始写代码

接下来开始写代码了,整个支付过程中最重要的是4个参数:1商户号,2 商户证书序列号,3 商户APIV3密钥 4 公众号appid,相关代码如下:

  1. package shitang.huidiao;
  2. public class WxConfig {
  3. /** 商户号 */
  4. public static final String merchantId = "你的商户号";
  5. /** 商户API私钥路径 */
  6. public static final String privateKeyPath = "D:/apiclient_key.pem";
  7. /** 商户证书序列号 */
  8. public static final String merchantSerialNumber = "xxxxxxx";
  9. /** 商户APIV3密钥 */
  10. public static final String apiV3key = "xxxxxxx";
  11. /**公众号appid */
  12. public static final String appid="xxxxxxx";
  13. }

至于这几个参数怎么获取,在百度上一搜就可以搜到。

整个代码根据微信提供的demo写的,比较简单,在这个页面可以看到相关url

开发指引-JSAPI支付 | 微信支付商户平台文档中心

我的代码是由maven构建的,pom.xml中相关的依赖为

  1. <dependency>
  2. <groupId>com.github.wechatpay-apiv3</groupId>
  3. <artifactId>wechatpay-apache-httpclient</artifactId>
  4. <version>0.2.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.github.wechatpay-apiv3</groupId>
  8. <artifactId>wechatpay-java</artifactId>
  9. <version>0.2.5</version>
  10. </dependency>

首先是支付界面,是个简单的jsp界面,就是向后台提供支付参数,但是因为是在微信的架构中,所以我仔细说说,代码如下

  1. <body>
  2. <!-- ${openid} -->
  3. <div style="display:flex;flex-direction:column;margin:12px;">
  4. <div style="display:flex;flex-direction:row;"><span>请选择要充值的金额:</span><div style="color:red;" id="showje"></div></div>
  5. <div style="display:flex;flex-direction:row;flex-wrap:wrap;margin-top:30px;">
  6. <div onclick="gai2('50')" style="margin-right: 15px;" class="am-btn am-btn-primary">50</div>
  7. <div onclick="gai2('100')" style="margin-right: 15px;" class="am-btn am-btn-primary">100</div>
  8. <div onclick="gai2('150')" style="margin-right: 15px;" class="am-btn am-btn-primary">150</div>
  9. <div onclick="gai2('250')" style="margin-right: 15px;" class="am-btn am-btn-primary">250</div>
  10. <div onclick="gai2('500')" style="margin-right: 15px;" class="am-btn am-btn-primary">500</div>
  11. </div>
  12. <div style="margin-top:10px;display:flex;flex-direction:row;"><input type="number" readonly="readonly" style="width: 100%;margin-right:15px;height: 30px;" name="jine" id="jine" value="50" onchange="gai()"/></div>
  13. <button style="margin-top:30px;" class="am-btn am-btn-primary" onclick="tijiao()">确定</button>
  14. </div>
  15. <script type="text/javascript">
  16. var jine=document.getElementById('jine');
  17. //var showje=document.getElementById("showje");
  18. //showje.innerHTML=jine.value;
  19. function gai2(num){
  20. var jine2=document.getElementById('jine');
  21. jine2.value=num;
  22. }
  23. function tijiao(){
  24. var jine2=document.getElementById('jine');
  25. if(jine2.value>500){
  26. alert('不能大于500');
  27. return;
  28. }
  29. $.ajax({
  30. type: "POST",
  31. url: "<%=basePath%>wxpay/yuzhifu",
  32. data: {
  33. jine:jine2.value,
  34. openid:'${openid}',
  35. number:'${number}',//工号
  36. NAME:'${NAME}',//员工姓名
  37. staff_no:'${staff_no}'//卡号
  38. },
  39. dataType:'json',
  40. cache: false,
  41. success: function(data){
  42. var code=data.code;
  43. var openid=data.openid;
  44. if(code==0){
  45. WeixinJSBridge.invoke('getBrandWCPayRequest', {
  46. "appId": data.appId, //公众号ID,由商户传入
  47. "timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
  48. "nonceStr": data.nonceStr, //随机串
  49. "package": data.package,
  50. "signType": "RSA", //微信签名方式:
  51. "paySign": data.paySign //微信签名
  52. },
  53. function(res) {
  54. console.log('res.err_msg='+res.err_msg);
  55. if (res.err_msg == "get_brand_wcpay_request:ok") {
  56. // 使用以上方式判断前端返回,微信团队郑重提示:
  57. //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
  58. alert('支付成功')
  59. window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
  60. }else{
  61. alert('支付失败')
  62. window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
  63. }
  64. });
  65. }else{
  66. var msg=data.msg;
  67. alert(msg+",openid="+openid);//回跳
  68. window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
  69. }
  70. }
  71. });
  72. }
  73. </script>
  74. </body>

这个页面的中心逻辑是向后台提供支付参数,后台在接受到支付参数之后,首先先向微信平台发送请求,进行预支付,相关代码如下:

  1. package com.bocomsoft.controller.wxpay;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.stream.Collectors;
  5. import java.util.stream.Stream;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Controller;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RequestParam;
  10. import org.springframework.web.bind.annotation.ResponseBody;
  11. import org.springframework.web.client.RestTemplate;
  12. import org.springframework.web.servlet.ModelAndView;
  13. import com.alibaba.fastjson.JSONObject;
  14. import com.wechat.pay.java.core.RSAAutoCertificateConfig;
  15. import com.wechat.pay.java.service.payments.jsapi.JsapiService;
  16. import com.wechat.pay.java.service.payments.jsapi.model.Amount;
  17. import com.wechat.pay.java.service.payments.jsapi.model.Payer;
  18. import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
  19. import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
  20. @RequestMapping("/wxpay")
  21. @Controller
  22. public class WxpayController {
  23. private RSAAutoCertificateConfig config;
  24. @Autowired
  25. WxService service;//这个是内部业务逻辑,不用管
  26. @RequestMapping("/test")
  27. @ResponseBody
  28. public JSONObject test() throws Exception {
  29. JSONObject json1 = new JSONObject();
  30. json1.put("code", 1);
  31. return json1;
  32. }
  33. @Autowired
  34. public void setConfig(){
  35. config = new RSAAutoCertificateConfig.Builder().merchantId(WxConfig.merchantId).privateKeyFromPath(WxConfig.privateKeyPath)
  36. .merchantSerialNumber(WxConfig.merchantSerialNumber).apiV3Key(WxConfig.apiV3key).build();
  37. }
  38. @RequestMapping("/yuzhifu")
  39. @ResponseBody
  40. public JSONObject yuzhifu(@RequestParam(value = "openid") String openid,
  41. @RequestParam(value = "number") String number, @RequestParam(value = "staff_no") String staff_no,
  42. @RequestParam(value = "jine") String jine,@RequestParam(value = "NAME") String NAME) throws Exception {
  43. Date now=new Date();
  44. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
  45. SimpleDateFormat sdf2=new SimpleDateFormat("yyyy-MM-dd");
  46. String shijian=sdf.format(now);
  47. String order_no=shijian+number;
  48. System.out.println("order_no="+order_no);
  49. JsapiService service = new JsapiService.Builder().config(config).build();
  50. // request.setXxx(val)设置所需参数,具体参数可见Request定义
  51. PrepayRequest request = new PrepayRequest();
  52. Amount amount = new Amount();
  53. amount.setTotal((int)(Double.parseDouble(jine)*100));
  54. //amount.setTotal(1);
  55. request.setAmount(amount);
  56. request.setAppid(WxConfig.appid);
  57. request.setMchid(WxConfig.merchantId);
  58. request.setDescription("食堂账户充值");
  59. request.setNotifyUrl("回调url");//这个回调url必须是https开头的
  60. request.setOutTradeNo(order_no);
  61. Payer payer = new Payer();
  62. payer.setOpenid(openid);
  63. request.setPayer(payer);
  64. PrepayResponse response = service.prepay(request);
  65. System.out.println(response.getPrepayId());
  66. String prepayid=response.getPrepayId();
  67. String nonceStr = WXPayUtil.generateNonceStr();
  68. long timeStamp = WXPayUtil.getCurrentTimestamp();
  69. String prepayidstr= "prepay_id=" + prepayid;
  70. String signType = "RSA";
  71. String signatureStr = Stream.of(WxConfig.appid, String.valueOf(timeStamp), nonceStr, prepayidstr)
  72. .collect(Collectors.joining("\n", "", "\n"));
  73. String paySign = WXPayUtil.getSign(signatureStr);
  74. resultjson.put("code", 0);
  75. resultjson.put("appId",WxConfig.appid);
  76. resultjson.put("timeStamp", String.valueOf(timeStamp));
  77. resultjson.put("nonceStr", nonceStr);
  78. resultjson.put("package", prepayidstr);
  79. resultjson.put("signType",signType);
  80. resultjson.put("paySign", paySign);
  81. resultjson.put("openid", openid);
  82. return resultjson;
  83. }
  84. }

这个是签名加密类,代码如下:

  1. package com.bocomsoft.controller.wxpay;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.net.URISyntaxException;
  7. import java.nio.charset.StandardCharsets;
  8. import java.security.InvalidKeyException;
  9. import java.security.NoSuchAlgorithmException;
  10. import java.security.PrivateKey;
  11. import java.security.SecureRandom;
  12. import java.security.Signature;
  13. import java.security.SignatureException;
  14. import java.util.Random;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import org.springframework.util.Base64Utils;
  18. import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
  19. public class WXPayUtil {
  20. private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  21. private static final Random RANDOM = new SecureRandom();
  22. public static String getSign(String signatureStr) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
  23. //replace 根据实际情况,不一定都需要
  24. //String replace = privateKey.replace("\\n", "\n");
  25. File file = new File(WxConfig.privateKeyPath);
  26. InputStream in = new FileInputStream(file);
  27. PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(in);
  28. Signature sign = Signature.getInstance("SHA256withRSA");
  29. sign.initSign(merchantPrivateKey);
  30. sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
  31. return Base64Utils.encodeToString(sign.sign());
  32. }
  33. /**
  34. * 获取随机字符串 Nonce Str
  35. *
  36. * @return String 随机字符串
  37. */
  38. public static String generateNonceStr() {
  39. char[] nonceChars = new char[32];
  40. for (int index = 0; index < nonceChars.length; ++index) {
  41. nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
  42. }
  43. return new String(nonceChars);
  44. }
  45. /**
  46. * 日志
  47. * @return
  48. */
  49. public static Logger getLogger() {
  50. Logger logger = LoggerFactory.getLogger("wxpay java sdk");
  51. return logger;
  52. }
  53. /**
  54. * 获取当前时间戳,单位秒
  55. * @return
  56. */
  57. public static long getCurrentTimestamp() {
  58. return System.currentTimeMillis()/1000;
  59. }
  60. /**
  61. * 获取当前时间戳,单位毫秒
  62. * @return
  63. */
  64. public static long getCurrentTimestampMs() {
  65. return System.currentTimeMillis();
  66. }
  67. public static void main(String[] args) {
  68. String signatureStr = "ssssss";
  69. try {
  70. String paySign = WXPayUtil.getSign(signatureStr);
  71. System.out.println("paySign="+paySign);
  72. } catch (Exception e) {
  73. e.printStackTrace();
  74. }
  75. }
  76. }

大家注意啊,坑人的地方主要两个地方,首先是签名方法,我才疏学浅,在官网上看得脑瓜都大了,没看明白,感谢伟大的互联网,让我找到了加密方法,先向大神表示感谢,再贴出原文的链接,供大家参考:微信支付V3 JSAPI签名_jsapi v3 签名_Coco_淳的博客-CSDN博客。第二个就是这个,大家注意到这了吗

 @Autowired
    public void setConfig(){
        config = new RSAAutoCertificateConfig.Builder().merchantId(WxConfig.merchantId).privateKeyFromPath(WxConfig.privateKeyPath)
                .merchantSerialNumber(WxConfig.merchantSerialNumber).apiV3Key(WxConfig.apiV3key).build();
    }

官网提供的demo是一个main函数,直接就执行下去了,但是在java后端的情况下,他会报错,The corresponding provider for the merchant already exists 大致的意思是apiV3的RSAConfig重复build,所以要在Controller中要以单例的方式实现,原文的参考链接如下:微信支付apiV3异常:The corresponding provider for the merchant already exists_迪八戈的博客-CSDN博客。当代码写到这一步了,用户在客户端也进行完支付了。然后进入下个阶段,这个阶段是微信平台向先前预支付设置的地址发送加了密的post请求,当我们接受到请求之后,首先是解密,然后是进行相关的业务内部运行逻辑。代码如下:

  1. package shitang.huidiao;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RequestHeader;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import org.springframework.web.client.RestTemplate;
  14. import com.alibaba.fastjson.JSONObject;
  15. import com.wechat.pay.java.core.RSAAutoCertificateConfig;
  16. import com.wechat.pay.java.core.notification.NotificationConfig;
  17. import com.wechat.pay.java.core.notification.NotificationParser;
  18. import com.wechat.pay.java.core.notification.RequestParam;
  19. @RestController
  20. @RequestMapping("/huidiao")
  21. public class HuidiaoController {
  22. NotificationConfig config;
  23. @Autowired
  24. public void setConfig(){
  25. config = new RSAAutoCertificateConfig.Builder()
  26. .merchantId(WxConfig.merchantId)
  27. .privateKeyFromPath(WxConfig.privateKeyPath)
  28. .merchantSerialNumber(WxConfig.merchantSerialNumber)
  29. .apiV3Key(WxConfig.apiV3key)
  30. .build();
  31. }
  32. @Autowired
  33. HuidiaoService service;
  34. @PostMapping(value = "/wxAppPayNotify")
  35. public String wxAppPayNotify( @RequestHeader("Wechatpay-Serial") String wechatPayCertificateSerialNumber,
  36. @RequestHeader("Wechatpay-Signature") String signature,
  37. @RequestHeader("Wechatpay-Timestamp") String timstamp,
  38. @RequestHeader("Wechatpay-Nonce") String nonce,
  39. @RequestBody String requestBody) {
  40. RequestParam requestParam = new RequestParam.Builder()
  41. .serialNumber(wechatPayCertificateSerialNumber)
  42. .nonce(nonce)
  43. .signature(signature)
  44. .timestamp(timstamp)
  45. // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
  46. .body(requestBody)
  47. .build();
  48. // 初始化 NotificationParser
  49. NotificationParser parser = new NotificationParser(config);
  50. // 验签并解密报文
  51. JSONObject decryptObject = parser.parse(requestParam,JSONObject.class);
  52. System.out.println("decryptObject="+decryptObject.toJSONString());
  53. String trade_state=decryptObject.getString("trade_state");
  54. JSONObject jsonResponse = new JSONObject();
  55. if(trade_state.equals("SUCCESS")) {
  56. //各种业务逻辑
  57. }else{
  58. //还是各种业务逻辑
  59. }
  60. jsonResponse.put("code", "SUCCESS");
  61. jsonResponse.put("message", "成功");
  62. return jsonResponse.toJSONString();
  63. }
  64. }

三 运行代码

该打包打包,该运行就运行,没啥说的,但是如果你的jdk版本是8,就会出现问题,(我的本机的jdk版本是11,是否会出现问题待验证,但是服务器的jdk版本是8,不能轻易去动)。

错误出现场景:用户在微信公众号里面发送一条消息后,接口收到的加密数据,在解密的时候报错。

错误内容:java.security.InvalidKeyException: Illegal key size

错误原因描述:JRE本身中自带的“local_policy.jar ”和“US_export_policy.jar”只支持128位密钥的加密算法

需要替换文件,原文链接如下:https://www.cnblogs.com/fswhq/p/16046179.html

最后希望大家共同进步。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/467396
推荐阅读
相关标签
  

闽ICP备14008679号