WeChat payment summary (JSAPI) (V3) (JAVA)

        A while ago, I built a WeChat payment-related function, and took many detours during the process. I'm here to share some information with you, and I hope you can benefit from it. Here, I will walk you through it step by step from beginning to end.

Table of contents

A related preparation

 2. Start writing code

3. Run the code


A related preparation

        To implement WeChat payment, you must first have a corresponding merchant account. The merchant number belongs to Party A, but in reality Party A often turns a blind eye to these, so the application work is often done by developers. But it doesn’t matter, just follow the step-by-step guidance provided by Tencent. If all the materials are available, it will take about one to two days. The super management role is bound to the legal person's WeChat phone number provided by Party A by default. If the WeChat user phone number bound to the super management is not a legal person, you need to upload an authorization letter. The format of the authorization letter is in the help instructions.

        After the merchant account is obtained, there are several things that need to be set up, which are related to development. The official has a clear explanation on this, the address is JSAPI Payment-Preparation before Access | WeChat Payment Merchant Platform Document Center . The project I am working on is the jsapi payment type, and its form is detailed in the Tencent payment document. The main requirements are the following: 1 Merchant ID: This can be seen in the background. 2 appid: This needs to be obtained from Party A. There is a many-to-many relationship between the merchant number and appid. To bind the merchant number and appid, you need to apply on the merchant platform, and then agree on the backend of the public platform corresponding to the appid. 3 Merchant certificate serial number. The merchant certificate serial number can be seen after applying for the certificate. You can see how to obtain it in the article WeChat Payment: API Merchant Certificate Serial Number SerialNo Acquisition-YES Development Framework Network , and you need to have super administrator rights. 4 Merchant APIV3 key. How to configure this is also clearly explained in the documentation.

        When you download the merchant certificate according to the document, you will get a zip file. Unzip it and you will get 4 files. Be sure to save them.

 2. Start writing code

Next, I started writing code. The most important parameters in the entire payment process are 4 parameters: 1 merchant number, 2 merchant certificate serial number, 3 merchant APIV3 key 4 official account appid. The relevant codes are as follows:

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";
}

As for how to obtain these parameters, you can search them on Baidu.

The entire code is written based on the demo provided by WeChat. It is relatively simple. You can see the relevant URL on this page.

Development Guide-JSAPI Payment | WeChat Payment Merchant Platform Document Center

My code is built by maven, and the relevant dependencies in pom.xml are

        <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>

The first is the payment interface, which is a simple jsp interface that provides payment parameters to the background. But because it is in the WeChat architecture, I will explain it carefully. The code is as follows

<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>

The central logic of this page is to provide payment parameters to the backend. After receiving the payment parameters, the backend first sends a request to the WeChat platform for prepayment. The relevant code is as follows:

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;
	}
}

This is the signature encryption class, the code is as follows:

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();
        }

	}
}

Everyone, please pay attention. There are two main areas of deception. The first is the signature method. I am not very knowledgeable. I was confused when I read it on the official website. I did not understand it. Thanks to the great Internet, I found the encryption method. I would like to ask the master first. To express my gratitude, I will post a link to the original text for your reference: WeChat Payment V3 JSAPI Signature_jsapi v3 Signature_Coco_Chun’s Blog-CSDN Blog . The second one is this. Have you noticed this?

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

The demo provided on the official website is a main function, which is executed directly. However, in the case of the Java backend, it will report an error. The corresponding provider for the merchant already exists. This roughly means that the RSAConfig of apiV3 is repeatedly built, so it must be in the Controller. To be implemented in a single instance, the reference link to the original text is as follows: WeChat payment apiV3 exception: The corresponding provider for the merchant already exists_Dibage's blog-CSDN blog . When the code is written to this step, the user has also completed the payment on the client. Then we enter the next stage. In this stage, the WeChat platform sends an encrypted post request to the address previously set for prepayment. When we receive the request, we first decrypt it, and then perform the relevant internal business operation logic. code show as below:

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();
	}

}

3. Run the code

It should be packaged and packaged, and it should be run when it is run. There is nothing to say, but if your jdk version is 8, problems will occur (my local jdk version is 11, whether there will be problems needs to be verified, but the server's jdk The version is 8 and cannot be changed easily).

Scenario where the error occurs: After the user sends a message in the WeChat official account, the encrypted data received by the interface will report an error during decryption.

Error content: java.security.InvalidKeyException: Illegal key size

Error cause description: The "local_policy.jar" and "US_export_policy.jar" that come with the JRE itself only support the 128-bit key encryption algorithm.

The file needs to be replaced. The original link is as follows: https://www.cnblogs.com/fswhq/p/16046179.html

Finally, I hope everyone can make progress together.

Guess you like

Origin blog.csdn.net/ZX5191/article/details/130495972