赞
踩
前一阵子做了一个微信支付相关的功能,期间走了不少的弯路。在这里给大家趟趟雷,希望大家能因此受益。在这里,我从头到尾一步步给大家捋顺。
目录
实现微信支付首先得有对应的商户号。商户号是甲方的,但是现实中甲方对这些往往是两眼一抹黑,所以关于申请的工作往往是开发者弄。但是没关系,跟着腾讯提供的引导一步步走就行了。如果材料齐全的话,大约一到两天的时间就会下来。超管的角色默认和甲方提供的法人电话微信进行绑定,如果超管所绑定的微信用户电话号不是法人,则需要上传授权函,授权函的格式在帮助说明中有。
等到商户号下来之后,有几个东西需要设置,这就和开发有关了。官方对此有比较明确的说明,地址为JSAPI支付-接入前准备 | 微信支付商户平台文档中心。我做的项目是jsapi支付类型,他的形式在腾讯支付文档中有详细的说明。主要是需要下面这几个:1 商户号:这个在后台里就能看到。2 appid:这个需要找甲方要。商户号和appid之间是多对多的关系。绑定商户号和appid需要在商户平台进行申请,然后在appid对应的公众平台后台进行同意。3 商户证书序列号。这个商户证书序列号在申请完证书之后就可以看到。在微信支付:API商户证书序列号serialNo获取-YES开发框架网这篇文章中可以看到如何获取,而且你需要有超级管理员权限。4 商户APIV3密钥。这个在文档中如何配置也有明确的说明。
当你按照文档下载商户证书时,你会得到一个zip文件,解压,你会得到4个文件,一定要保存好。
接下来开始写代码了,整个支付过程中最重要的是4个参数:1商户号,2 商户证书序列号,3 商户APIV3密钥 4 公众号appid,相关代码如下:
- package shitang.huidiao;
-
- public class WxConfig {
- /** 商户号 */
- public static final String merchantId = "你的商户号";
- /** 商户API私钥路径 */
- public static final String privateKeyPath = "D:/apiclient_key.pem";
- /** 商户证书序列号 */
- public static final String merchantSerialNumber = "xxxxxxx";
- /** 商户APIV3密钥 */
- public static final String apiV3key = "xxxxxxx";
- /**公众号appid */
- public static final String appid="xxxxxxx";
- }
至于这几个参数怎么获取,在百度上一搜就可以搜到。
整个代码根据微信提供的demo写的,比较简单,在这个页面可以看到相关url
我的代码是由maven构建的,pom.xml中相关的依赖为
- <dependency>
- <groupId>com.github.wechatpay-apiv3</groupId>
- <artifactId>wechatpay-apache-httpclient</artifactId>
- <version>0.2.2</version>
- </dependency>
- <dependency>
- <groupId>com.github.wechatpay-apiv3</groupId>
- <artifactId>wechatpay-java</artifactId>
- <version>0.2.5</version>
- </dependency>
首先是支付界面,是个简单的jsp界面,就是向后台提供支付参数,但是因为是在微信的架构中,所以我仔细说说,代码如下
- <body>
- <!-- ${openid} -->
- <div style="display:flex;flex-direction:column;margin:12px;">
- <div style="display:flex;flex-direction:row;"><span>请选择要充值的金额:</span><div style="color:red;" id="showje"></div></div>
- <div style="display:flex;flex-direction:row;flex-wrap:wrap;margin-top:30px;">
- <div onclick="gai2('50')" style="margin-right: 15px;" class="am-btn am-btn-primary">50</div>
- <div onclick="gai2('100')" style="margin-right: 15px;" class="am-btn am-btn-primary">100</div>
- <div onclick="gai2('150')" style="margin-right: 15px;" class="am-btn am-btn-primary">150</div>
- <div onclick="gai2('250')" style="margin-right: 15px;" class="am-btn am-btn-primary">250</div>
- <div onclick="gai2('500')" style="margin-right: 15px;" class="am-btn am-btn-primary">500</div>
- </div>
- <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>
- <button style="margin-top:30px;" class="am-btn am-btn-primary" onclick="tijiao()">确定</button>
- </div>
- <script type="text/javascript">
- var jine=document.getElementById('jine');
- //var showje=document.getElementById("showje");
- //showje.innerHTML=jine.value;
-
- function gai2(num){
- var jine2=document.getElementById('jine');
- jine2.value=num;
- }
- function tijiao(){
- var jine2=document.getElementById('jine');
- if(jine2.value>500){
- alert('不能大于500');
- return;
- }
- $.ajax({
- type: "POST",
- url: "<%=basePath%>wxpay/yuzhifu",
- data: {
- jine:jine2.value,
- openid:'${openid}',
- number:'${number}',//工号
- NAME:'${NAME}',//员工姓名
- staff_no:'${staff_no}'//卡号
- },
- dataType:'json',
- cache: false,
- success: function(data){
- var code=data.code;
- var openid=data.openid;
- if(code==0){
- WeixinJSBridge.invoke('getBrandWCPayRequest', {
- "appId": data.appId, //公众号ID,由商户传入
- "timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
- "nonceStr": data.nonceStr, //随机串
- "package": data.package,
- "signType": "RSA", //微信签名方式:
- "paySign": data.paySign //微信签名
- },
- function(res) {
- console.log('res.err_msg='+res.err_msg);
- if (res.err_msg == "get_brand_wcpay_request:ok") {
- // 使用以上方式判断前端返回,微信团队郑重提示:
- //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
-
- alert('支付成功')
- window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
- }else{
- alert('支付失败')
- window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
- }
- });
- }else{
- var msg=data.msg;
-
- alert(msg+",openid="+openid);//回跳
- window.location.href="<%=basePath%>setmeal/login_login.do?openid="+openid;
- }
- }
- });
- }
-
-
- </script>
- </body>
这个页面的中心逻辑是向后台提供支付参数,后台在接受到支付参数之后,首先先向微信平台发送请求,进行预支付,相关代码如下:
- package com.bocomsoft.controller.wxpay;
-
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.ResponseBody;
- import org.springframework.web.client.RestTemplate;
- import org.springframework.web.servlet.ModelAndView;
-
- import com.alibaba.fastjson.JSONObject;
- import com.wechat.pay.java.core.RSAAutoCertificateConfig;
- import com.wechat.pay.java.service.payments.jsapi.JsapiService;
- import com.wechat.pay.java.service.payments.jsapi.model.Amount;
- import com.wechat.pay.java.service.payments.jsapi.model.Payer;
- import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
- import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
-
- @RequestMapping("/wxpay")
- @Controller
- public class WxpayController {
- private RSAAutoCertificateConfig config;
- @Autowired
- WxService service;//这个是内部业务逻辑,不用管
- @RequestMapping("/test")
- @ResponseBody
- public JSONObject test() throws Exception {
- JSONObject json1 = new JSONObject();
-
- json1.put("code", 1);
- return json1;
- }
- @Autowired
- public void setConfig(){
- config = new RSAAutoCertificateConfig.Builder().merchantId(WxConfig.merchantId).privateKeyFromPath(WxConfig.privateKeyPath)
- .merchantSerialNumber(WxConfig.merchantSerialNumber).apiV3Key(WxConfig.apiV3key).build();
- }
-
-
-
-
- @RequestMapping("/yuzhifu")
- @ResponseBody
- public JSONObject yuzhifu(@RequestParam(value = "openid") String openid,
- @RequestParam(value = "number") String number, @RequestParam(value = "staff_no") String staff_no,
- @RequestParam(value = "jine") String jine,@RequestParam(value = "NAME") String NAME) throws Exception {
-
- Date now=new Date();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
- SimpleDateFormat sdf2=new SimpleDateFormat("yyyy-MM-dd");
- String shijian=sdf.format(now);
- String order_no=shijian+number;
- System.out.println("order_no="+order_no);
-
-
-
-
- JsapiService service = new JsapiService.Builder().config(config).build();
- // request.setXxx(val)设置所需参数,具体参数可见Request定义
- PrepayRequest request = new PrepayRequest();
- Amount amount = new Amount();
- amount.setTotal((int)(Double.parseDouble(jine)*100));
- //amount.setTotal(1);
- request.setAmount(amount);
- request.setAppid(WxConfig.appid);
- request.setMchid(WxConfig.merchantId);
- request.setDescription("食堂账户充值");
- request.setNotifyUrl("回调url");//这个回调url必须是https开头的
- request.setOutTradeNo(order_no);
- Payer payer = new Payer();
- payer.setOpenid(openid);
- request.setPayer(payer);
- PrepayResponse response = service.prepay(request);
- System.out.println(response.getPrepayId());
- String prepayid=response.getPrepayId();
- String nonceStr = WXPayUtil.generateNonceStr();
- long timeStamp = WXPayUtil.getCurrentTimestamp();
- String prepayidstr= "prepay_id=" + prepayid;
- String signType = "RSA";
- String signatureStr = Stream.of(WxConfig.appid, String.valueOf(timeStamp), nonceStr, prepayidstr)
- .collect(Collectors.joining("\n", "", "\n"));
- String paySign = WXPayUtil.getSign(signatureStr);
-
- resultjson.put("code", 0);
- resultjson.put("appId",WxConfig.appid);
- resultjson.put("timeStamp", String.valueOf(timeStamp));
- resultjson.put("nonceStr", nonceStr);
- resultjson.put("package", prepayidstr);
- resultjson.put("signType",signType);
- resultjson.put("paySign", paySign);
- resultjson.put("openid", openid);
-
-
- return resultjson;
- }
- }
这个是签名加密类,代码如下:
- package com.bocomsoft.controller.wxpay;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.URISyntaxException;
- import java.nio.charset.StandardCharsets;
- import java.security.InvalidKeyException;
- import java.security.NoSuchAlgorithmException;
- import java.security.PrivateKey;
- import java.security.SecureRandom;
- import java.security.Signature;
- import java.security.SignatureException;
- import java.util.Random;
-
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.util.Base64Utils;
-
- import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
-
- public class WXPayUtil {
- private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
-
- private static final Random RANDOM = new SecureRandom();
-
-
- public static String getSign(String signatureStr) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
- //replace 根据实际情况,不一定都需要
- //String replace = privateKey.replace("\\n", "\n");
- File file = new File(WxConfig.privateKeyPath);
-
- InputStream in = new FileInputStream(file);
- PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(in);
-
- Signature sign = Signature.getInstance("SHA256withRSA");
- sign.initSign(merchantPrivateKey);
- sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
- return Base64Utils.encodeToString(sign.sign());
- }
-
- /**
- * 获取随机字符串 Nonce Str
- *
- * @return String 随机字符串
- */
- public static String generateNonceStr() {
- char[] nonceChars = new char[32];
- for (int index = 0; index < nonceChars.length; ++index) {
- nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
- }
- return new String(nonceChars);
- }
-
-
- /**
- * 日志
- * @return
- */
- public static Logger getLogger() {
- Logger logger = LoggerFactory.getLogger("wxpay java sdk");
- return logger;
- }
-
- /**
- * 获取当前时间戳,单位秒
- * @return
- */
- public static long getCurrentTimestamp() {
- return System.currentTimeMillis()/1000;
- }
-
- /**
- * 获取当前时间戳,单位毫秒
- * @return
- */
- public static long getCurrentTimestampMs() {
- return System.currentTimeMillis();
- }
- public static void main(String[] args) {
- String signatureStr = "ssssss";
- try {
- String paySign = WXPayUtil.getSign(signatureStr);
- System.out.println("paySign="+paySign);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
- }
大家注意啊,坑人的地方主要两个地方,首先是签名方法,我才疏学浅,在官网上看得脑瓜都大了,没看明白,感谢伟大的互联网,让我找到了加密方法,先向大神表示感谢,再贴出原文的链接,供大家参考:微信支付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请求,当我们接受到请求之后,首先是解密,然后是进行相关的业务内部运行逻辑。代码如下:
- package shitang.huidiao;
-
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestHeader;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.client.RestTemplate;
-
- import com.alibaba.fastjson.JSONObject;
- import com.wechat.pay.java.core.RSAAutoCertificateConfig;
- import com.wechat.pay.java.core.notification.NotificationConfig;
- import com.wechat.pay.java.core.notification.NotificationParser;
- import com.wechat.pay.java.core.notification.RequestParam;
-
- @RestController
- @RequestMapping("/huidiao")
- public class HuidiaoController {
- NotificationConfig config;
- @Autowired
- public void setConfig(){
- config = new RSAAutoCertificateConfig.Builder()
- .merchantId(WxConfig.merchantId)
- .privateKeyFromPath(WxConfig.privateKeyPath)
- .merchantSerialNumber(WxConfig.merchantSerialNumber)
- .apiV3Key(WxConfig.apiV3key)
- .build();
- }
- @Autowired
- HuidiaoService service;
- @PostMapping(value = "/wxAppPayNotify")
- public String wxAppPayNotify( @RequestHeader("Wechatpay-Serial") String wechatPayCertificateSerialNumber,
- @RequestHeader("Wechatpay-Signature") String signature,
- @RequestHeader("Wechatpay-Timestamp") String timstamp,
- @RequestHeader("Wechatpay-Nonce") String nonce,
- @RequestBody String requestBody) {
-
- RequestParam requestParam = new RequestParam.Builder()
- .serialNumber(wechatPayCertificateSerialNumber)
- .nonce(nonce)
- .signature(signature)
- .timestamp(timstamp)
- // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
-
- .body(requestBody)
- .build();
-
-
-
- // 初始化 NotificationParser
- NotificationParser parser = new NotificationParser(config);
-
- // 验签并解密报文
- JSONObject decryptObject = parser.parse(requestParam,JSONObject.class);
- System.out.println("decryptObject="+decryptObject.toJSONString());
-
- String trade_state=decryptObject.getString("trade_state");
- JSONObject jsonResponse = new JSONObject();
- if(trade_state.equals("SUCCESS")) {
- //各种业务逻辑
- }else{
- //还是各种业务逻辑
- }
- jsonResponse.put("code", "SUCCESS");
- jsonResponse.put("message", "成功");
- return jsonResponse.toJSONString();
- }
-
- }
该打包打包,该运行就运行,没啥说的,但是如果你的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
最后希望大家共同进步。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。