微信支付总结(JSAPI)(V3)(JAVA)

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

目录

一 相关准备

 二 开始写代码

三 运行代码


一 相关准备

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

        等到商户号下来之后,有几个东西需要设置,这就和开发有关了。官方对此有比较明确的说明,地址为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

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

我的代码是由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

最后希望大家共同进步。

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
注意要点: 1,Topay里面的参数要填好:appid,appsecret,mch_id,partnerkey,spbill_create_ip 2,openid 需要微信授权获取到 3,每次支付orderNo要不同 openid参考实例: 1,授权链接地址:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxba3445566677&redirect_uri=http://www.acc.com/weixin/pay/paydispatcher&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect 2,转向处理地址:通过第一个链接微信会把code传过来,之前参数获取就行 @RequestMapping(value = "/paydispatcher", method = { RequestMethod.GET }) public void payDispatcher(HttpServletRequest request, HttpServletResponse response) throws Exception { String code = request.getParameter("code"); String msg=""; if(code==null||code.equals("")){ msg="获取微信Code失败!"; request.setAttribute("msg" ,msg); request.getRequestDispatcher("/jsp/login.jsp").forward(request,response); }else{ WeixinUtil util = new WeixinUtil(); UserAccessToken token = (UserAccessToken) request.getSession().getAttribute("UserAccessToken"); if(null==token){ token = util.getAccessToken3(Constants.APPID, Constants.SECRET,code); request.getSession().setAttribute("UserAccessToken",token); } request.setAttribute("openid", token.getOpenid()); request.setAttribute("accessToken", token.getAccessToken()); request.setAttribute("refreshToken", token.getRefreshToken()); request.setAttribute("expiresIn", token.getExpiresIn()); request.getRequestDispatcher("/pay/index.jsp").forward(request,response); } } // 获取用户openid accesstoken public static UserAccessToken getAccessToken3(String appid , String appsecret,String code) { UserAccessToken accessToken = null; String requestUrl = Constants.GET_OPENID_ACCESSTOKEN_URL.replace("APPID" , appid).replace("APPSECRET" , appsecret).replace("CODE" , code); String json = httpRequest(requestUrl , "GET" , null); JSONObject jsonObject = JSONObject.fromObject(json); // 如果请求成功 if (null != jsonObject) { try { accessToken = new UserAccessToken(); accessToken.setAccessToken(jsonObject.getString("access_token")); accessToken.setRefreshToken(jsonObject.getString("refresh_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in")); accessToken.setOpenid(jsonObject.getString("openid")); accessToken.setScope(jsonObject.getString("scope")); } catch (Exception e) { accessToken = null; // 获取token失败 System.out.println("获取token失败 errcode:{} errmsg:{}"); } } return accessToken; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值