preface

Wechat login webpage authorization and APP authorization wechat JSAPI payment wechat APP payment wechat APP and JSAPI refund Alipay mobile phone website payment Alipay APP payment alipay refund above I have put personal public number, search a search: JAVA thief ship, the end of the article has a public number two-dimensional code! Feel that personal development will be used in the future can pay attention to oh! Take fewer detours…

The official documentation

  • Js-sdk documentation
  • JSAPI payment documentation

The preparatory work

Public Account Configuration
  • Bind domain name (this domain name can be used to call the JS interface opened by wechat, and the front-end domain name can be configured here) first log in to the wechat public platform and enter the “Function Setting” of “Public Account Setting” and fill in the “JS interface security domain name”.

  • Fill in interface configuration information (for cooperating with wechat server authentication)

    Development – Basic configuration, configure URL, Token and generate EncodingAESKey, after configuration at this time click submit is not enough, need to write the back-end interface and wechat authentication, the following code.

    	@apiOperation (" wechat authentication address ")
    	@GetMapping("/baseCallback")
    	public String baseCallback(String signature,String timestamp,String nonce,String echostr) throws Exception {
    		return echostr;
    	}
    Copy the code
  • Configure an IP address whitelist

    To obtain common accessToken, log in to wechat Public Platform-Development-Basic Configuration and add the server IP address to the IP whitelist in advance; otherwise, the call fails

Merchant platform configuration
  • Set the payment directory (only when it is configured can the wechat cashier be pulled up and the front-end domain name be configured, otherwise the Url of the current page will be reported as unregistered error)

    Login wechat Payment merchant platform (pay.weixin.qq.com) –> Product Center –> Development configuration, setting generally takes effect within 5 minutes.

  • Payment authorization directory verification instructions

    1. If the payment authorization directory is set to a top-level domain name (for example, www.weixin.com/), only the top-level domain name is verified, but the suffix is not verified.

    2. If the payment authorization directory is set to a multi-level directory, full matching will be performed. For example, if the payment authorization directory is set to www.weixin.com/abc/123/, then…

Process steps

How to use JSSDK

This document mainly looks at 1 overview and 14 wechat payment, as shown below.

The main steps
  • Front-end fetching injection parameters (i.e., Step 3 of the JSSDK documentation Overview)

    Before payment, the front end requests the background to obtain the parameters required in Step 3 in the figure above, and the injection is successful. The front end had better configure the global configuration, because the service may expand, and other interfaces will be required, such as the sharing interface

  • The front end gets the parameters required to evoke wechat cashier

    After the injection is successful -> the front end clicks wechat Pay -> the back end places an order to wechat -> After the order is successful, prepay_id is obtained -> return the required parameters to the front end -> evoke the cashier

Back-end code implementation

Introduction of depend on
<! --> <dependency> <groupId>com.github. Wxpay </groupId> <artifactId>wxpay </artifactId> <version>0.03.</version>
			</dependency>
Copy the code
Configuration parameters

application.yml

# Wechat related configuration
wx:
  # Merchant ID (wechat Payment platform - Account Center - Account information)
  MCH_ID: 
  # APP_ID(wechat open platform or official account)
  H_APP_ID: 
  Secret key (wechat open platform or public account search)
  H_APP_SECRET: 
  # Decryption string used for message encryption and decryption (wechat public number - basic configuration search)
  H_ENCODINGAESKEY:
  # token (wechat public number - basic configuration lookup)
  H_TOKEN: 
  Payment KEY (wechat Payment platform - Account Center - API security - API KEY)
  H_KEY: 
  Contents contained in the Payment Merchant certificate (wechat Payment platform - Account Center -API security -API Certificate)
  H_CERT_PATH: 
  Pay successfully callback address
  WX_CALLBACK_URL: 
Copy the code

YmlParament

@Component
@Data
public class YmlParament {
	/* wechat related fields */
	@Value("${wx.H_APP_ID}")
	private String h_app_id;
	@Value("${wx.H_APP_SECRET}")
	private String h_app_secret;
	@Value("${wx.H_ENCODINGAESKEY}")
	private String h_encodingaeskey;
	@Value("${wx.H_TOKEN}")
	private String h_token;
	@Value("${wx.MCH_ID}")
	private String mch_id;
	@Value("${wx.H_KEY}")
	private String h_key;
	@Value("${wx.H_CERT_PATH}")
	private String h_cert_path;
    @Value("${wx.WX_CALLBACK_URL}")
	private String wx_callback_url;

Copy the code
Obtain front-end Config interface configuration parameters (see Appendix 1 of the JSSDK documentation)

Before generating a signature, you must know jsapi_ticket. Jsapi_ticket is a temporary ticket used by the public account to invoke the wechat JS interface. Normally, the jSAPi_ticket validity period is 7200 seconds. The value can be obtained by access_token. The number of API calls used to obtain jSAPi_ticket is limited. If you refresh jSAPi_ticket frequently, the API calls are limited and services are affected. Therefore, developers must cache jSAPi_ticket globally in their own services.

  • Application Enables scheduled tasks for periodic refreshing
@EnableSchedulingFor details about how to start a scheduled task, see http://cron.qqe2.com/
Copy the code
  • Configuring global Cache

WxParament

/** * Get ACCESS_TOKEN * refresh rule: * 1, set the value * 2, refresh once an hour */
	public static String ACCESS_TOKEN;
	/** * Get JSAPI_TICKET * refresh rule: * 1. Set this value * 2. Refresh once an hour */
	public static String JSAPI_TICKET;
Copy the code
  • Time to refresh

InitServiceImpl

	@Autowired
	private YmlParament ymlParament;

	@PostConstruct
	@Override
	public void init(a) {
		initAccessToken();
		initJsapiTicket();
	}

	/** * Initializes AccessToken once an hour *cron expressions can be generated online: https://cron.qqe2.com/ */
	@Scheduled(cron = "0 0 0/1 * * ?" )
	@Override
	public void initAccessToken(a) {
		try {
			WxParament.ACCESS_TOKEN = GetToken.getAccessToken(ymlParament.getH_app_id(), ymlParament.getH_app_secret());
		} catch (Exception e) {
			log.error("<====initAccessToken initialization failed! = = >" + e);
			e.printStackTrace();
		}
		log.info("<==== initAccessToken successfully initialized with value ==>" + WxParament.ACCESS_TOKEN);
	}

	/** * Initializes JSAPI_TICKET once an hour */
	@Scheduled(cron = "0 0 0/1 * * ?" )
	@Override
	public void initJsapiTicket(a) {
		try {
			log.info("<==== refreshing JSAPI_TICKET ==>");
			WxParament.JSAPI_TICKET = GetToken.getTicket(ymlParament.getH_app_id(), ymlParament.getH_app_secret(),
					WxParament.ACCESS_TOKEN);
		} catch (Exception e) {
			log.error("<====initJsapiTicket initialization failed! = = >" + e);
			e.printStackTrace();
		}
		log.info("<==== JSAPI_TICKET is refreshed successfully, and the value is ==>" + WxParament.JSAPI_TICKET);
	}
Copy the code

GetToken

/** * ~~~~~~~~~ step 1 ~~~~~~~~~ * Valid for two hours, pay attention to cache to obtain access_token, remember to configure IP whitelist * Refer to the following documents to obtain access_token (valid for 7200 seconds, Developers must cache access_token globally on their own services) :  * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html *@param appid
	 * @param secret
	 * @throws Exception 
	 */
	public static String getAccessToken(String appid, String secret) throws Exception {
		String res=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token"."grant_type=client_credential&appid=" + appid + "&secret=" + secret);
			String access_token=JSON.parseObject(res).getString("access_token");
			if(IsNull.isNull(access_token)) {
			throw new Exception(res);
		}
		 return access_token;
	}

	/** * ~~~~~~~~~ step 2 ~~~~~~~~~ * Valid for two hours, note that the cache uses the access_token obtained in step 1 to request jsapi_ticket (valid for 7200 seconds, Developers must cache jSAPi_ticket globally on their own services) :  * https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi *@param appid
	 * @param secret
	 * @paramAccessToken If accessToken is passed null, the access_token is obtained again. If accessToken has a value, Ticket * is obtained directly@throws Exception
	 */
	public static String getTicket(String appid, String secret,String accessToken) throws Exception { String token=IsNull.isNull(accessToken)? getAccessToken(appid, secret):accessToken; String res=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/ticket/getticket"."access_token=" + token + "&type=jsapi");
		String ticket=JSON.parseObject(res).getString("ticket");
		if(IsNull.isNull(ticket)) {
			throw new Exception(res);
		}
		return ticket;
	}
	
	/** ** jsapi_ticket is a temporary ticket used by the public account to invoke the JS interface of wechat * Unified JS signature of the foreground. The reference instructions are as follows * 1. Service description: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#3 * 2, signature algorithm: 1 - JS - SDK access signature algorithm: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 *@paramJsapi_ticket: the value must be entered. Each valid time is two hours. An error message is displayed if the value is frequently obtained@paramUrl URL (the URL of the current payment page, excluding # and the following part) *@return* /
	public static Map<String, String> getJsSignature(String jsapi_ticket, String url) {
		return sign(jsapi_ticket, url);
	}
	/* Signature algorithm */
	private static Map<String, String> sign(String jsapi_ticket, String url) {
		Map<String, String> ret = new HashMap<String, String>();
		String nonce_str = UUID.randomUUID().toString();;
		String timestamp = Long.toString(System.currentTimeMillis() / 1000);
		String signature = null;
		// Note that the parameter names must be all lowercase and must be in order
		String string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;
		try {
			MessageDigest crypt = MessageDigest.getInstance("SHA-1");
			crypt.reset();
			crypt.update(string1.getBytes("UTF-8"));
			signature = byteToHex(crypt.digest());
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ret.put("url", url);
		ret.put("jsapi_ticket", jsapi_ticket);
		ret.put("nonceStr", nonce_str);
		ret.put("timestamp", timestamp);
		ret.put("signature", signature);
		return ret;
	}

	private static String byteToHex(final byte[] hash) {
		Formatter formatter = new Formatter();
		for (byte b : hash) {
			formatter.format("%02x", b);
		}
		String result = formatter.toString();
		formatter.close();
		return result;
	}
Copy the code

HttpUtil

public static String get(String urlStr, Map<String, String> parameters) throws IOException {
		URL url = new URL(urlStr);
		HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
		httpURLConnection.setDoInput(true);
		httpURLConnection.setDoOutput(true); // Set the connection to output
		httpURLConnection.setRequestMethod("GET"); // Set the request mode
		httpURLConnection.setRequestProperty("charset"."utf-8");
		PrintWriter pw = new PrintWriter(new BufferedOutputStream(httpURLConnection.getOutputStream()));

		StringBuffer parameter = new StringBuffer();
		parameter.append("1 = 1");
		for (Entry<String, String> entry : parameters.entrySet()) {
			parameter.append("&" + entry.getKey() + "=" + entry.getValue());
		}
		pw.write(parameter.toString());// Write data to the connection (equivalent to sending data to the server)
		pw.flush();
		pw.close();
    
		BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));
		String line = null;
		StringBuilder sb = new StringBuilder();
		while((line = br.readLine()) ! =null) { // Read data
			sb.append(line + "\n");
	}
		br.close();
	return sb.toString();
	}
Copy the code

JacksonUtil

public class JacksonUtil {
    public static String parseString(String body, String field) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node;
        try {
            node = mapper.readTree(body);
            JsonNode leaf = node.get(field);
            if(leaf ! =null) {
                returnleaf.asText(); }}catch (IOException e) {
        	e.printStackTrace();
        }
        return null; }}Copy the code
  • Wechat Pay initialization parameters
	@apiOperation (" wechat Pay initialization parameters ")
	@PostMapping("getJsSignature")
	public R getJsSignature(@RequestBody String body){
        // (the URL of the current payment page, excluding # and the following part)
		String url = JacksonUtil.parseString(body, "url");
		if(IsNull.isNull(url)) {
			return R.error("Parameters cannot be empty!");
		}
		Map<String, Object> res = new HashMap<>();
		Map<String, String> jsSignature = GetToken.getJsSignature(WxParament.JSAPI_TICKET, url);
		res.put("appId", ymlParament.getH_app_id());
		res.put("timestamp",jsSignature.get("timestamp"));
		res.put("nonceStr",jsSignature.get("nonceStr"));
		res.put("signature",jsSignature.get("signature"));
		return R.ok(res);
	}
Copy the code
To obtain the openid

Can I refer to the previous tweets: mp.weixin.qq.com/s/FrhpFTENj…

Unified order on wechat

WeChat unified order interface document: pay.weixin.qq.com/wiki/doc/ap…

  • Initialize the wechat Pay configuration
@Component
public class WxConfig {
   @Autowired
   private YmlParament ymlParament;
   
   /** * Initialize the wechat Pay configuration *@throws Exception 
    */
   @Bean(autowire = Autowire.BY_NAME,value = WxParament.H5_WX_PAY)
   public WXPay setH5WXPay(a) throws Exception {
      return new WXPay(new WxPayConfig(
            ymlParament.getH_cert_path(),
            ymlParament.getH_app_id(),
            ymlParament.getMch_id(),
            ymlParament.getH_key()));
   }
 }
Copy the code

WxPayConfig

public class WxPayConfig implements WXPayConfig {
	private byte[] certData;
	private String appID;
	private String mchID;
	private String key;

	public WxPayConfig(String certPath, String appID,String mchID,String key) throws Exception {
		File file = new File(certPath);
		InputStream certStream = new FileInputStream(file);
		this.certData = new byte[(int) file.length()];
		certStream.read(this.certData);
		certStream.close();
		this.appID = appID;
		this.mchID = mchID;
		this.key = key; }}Copy the code
  • Wechat ordering interface, key code (service layer)
	@Resource(name = WxParament.H5_WX_PAY)
	private WXPay wxH5Pay;

/* wechat unified order */
   private Map<String, String> wxUnifiedOrder(String orderNo, String orderFee, String requestIp, String openid) throws RuntimeException {
      Map<String, String> data = new HashMap<String, String>();
      data.put("nonce_str", WXPayUtil.generateNonceStr());
      data.put("body"."I'll take the order.");
      data.put("out_trade_no", orderNo);
      data.put("sign_type"."MD5");
      data.put("total_fee", orderFee);
      data.put("spbill_create_ip", requestIp);
      data.put("openid", openid);
      data.put("notify_url",ymlParament.getWx_callback_url());
      data.put("trade_type"."JSAPI"); // JSAPI payment is specified here
      Map<String, String> wxOrderResult = WxPay.unifiedorder(data,wxH5Pay);
     if("FAIL".equals(wxOrderResult.get("return_code"))) {throw new RuntimeException(wxOrderResult.get("return_msg"));
     }
        /* IsNull Custom, mainly determines the non-null */
      if (IsNull.isNull(wxOrderResult.get("prepay_id"))) {
         throw new RuntimeException("Prepay_id returned empty after successful wechat Pay order");
      }
      return wxOrderResult;

   }

	@Override
	public Map<String, String> insertWxH5Pay(String orderNo,String orderFee, String requestIp, String openid) {
		try {
			/* wechat order */
			Map<String, String> wxOrderResult = wxUnifiedOrder(orderNo, orderFee, requestIp, openid);
			Map<String, String> chooseWXPay = new HashMap<>();
            /* Here I have a big hole, where the key value is camel, but the APP payment is not!! * /
			chooseWXPay.put("appId", ymlParament.getH_app_id());
			chooseWXPay.put("timeStamp", WxUtils.create_timestamp());
			chooseWXPay.put("nonceStr", wxOrderResult.get("nonce_str"));
			chooseWXPay.put("package"."prepay_id=" + wxOrderResult.get("prepay_id"));
			chooseWXPay.put("signType"."MD5");
			String signature = WXPayUtil.generateSignature(chooseWXPay, ymlParament.getH_key());
			chooseWXPay.put("paySign", signature);
			return chooseWXPay;
		} catch (Exception e) {
			e.printStackTrace();
			throw newRuntimeException(e.getMessage()); }}Copy the code
  • Controller layer (omitted)
Wechat Pay successfully callback
  • The key code
   @Resource(name = WxParament.H5_WX_PAY)
   private WXPay wxH5Pay;

   @apiOperation (" wechat Pay callback ")
   @PostMapping("callback")
   public String callback(HttpServletRequest request) throws Exception {
   	try {
   		// 1
   		Map<String, String> params = getMapByRequest(request);
   		log.info("Wechat callback back !!!!" + params);
   		// 2
           checkCallbackWxPay(params);
   		// Business logic
   		return wxPaySuccess();
   	} catch (Exception e) {
   		e.printStackTrace();
   		returnwxPayFail(e.getMessage()); }}/** * get request parameters * from wechat@param request
    * @return
    * @throws Exception
    */
   private static Map<String, String> getMapByRequest(HttpServletRequest request) throws Exception{
   	InputStream inStream = request.getInputStream();
   	ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
   	byte[] buffer = new byte[1024];
   	int len = 0;
   	while((len = inStream.read(buffer)) ! = -1) {
   		outSteam.write(buffer, 0, len);
   	}
   	Map<String, String> ret= WXPayUtil.xmlToMap(new String(outSteam.toByteArray(), "utf-8"));
   	outSteam.close();
   	inStream.close();
   	return ret;
   }	
   
   / * check * /
   private void checkCallbackWxPay(Map<String, String> params)
   		throws Exception {
   	if ("FAIL".equals(params.get("result_code"))) {
   		throw new Exception("Wechat Payment failed");
   	}
   	if(! checkWxCallbackSing(params, wxH5Pay)) {throw new Exception("Wechat Pay callback signature authentication failed");
   	}
   	// Check the amount
       // Check whether the order is duplicated
       / /... The business logic
   }
   
/ * * * * * check WeChat callback signature https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7@paramWp to:@Autowired WXPay
    */
   private static boolean checkWxCallbackSing(Map<String, String> notifyMap, WXPay wp) throws Exception {
   	return wp.isPayResultNotifySignatureValid(notifyMap);
   }

   /** * wechat Pay returns parameter result encapsulation */
   private static String wxPaySuccess(a) throws Exception {
   	Map<String, String> succResult = new HashMap<String, String>();
   	succResult.put("return_code"."SUCCESS");
   	succResult.put("return_msg"."OK");
   	return WXPayUtil.mapToXml(succResult);
   }
   / * * *@paramMess Error message *@returnWechat returned wrong my prompt *@throws Exception
    */
   private static String wxPayFail(String mess) throws Exception {
   	Map<String, String> succResult = new HashMap<String, String>();
   	succResult.put("return_code"."FAIL");
   	succResult.put("return_msg", IsNull.isNull(mess)?"Custom exception error!":mess);
   	return WXPayUtil.mapToXml(succResult);
   }
Copy the code
supplement

If you do not want to verify the signature, there is another way to determine whether the payment is successful, that is, call the wechat order query interface to check whether the payment is successful

  • The key code
	/* Call wechat order query interface */
   			Map<String, String> orderqueryRes = orderquery(wXH5Pay,params.get("out_trade_no"));
   			/* Transaction successful */
   			if (!"SUCCESS".equals(orderqueryRes.get("trade_state"))) {throw new Exception("<=== wechat payment failure ====> Order number is ["+ params.get("out_trade_no") +"Order for]");
   			}

   / * * * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 * / query pay results
   private static Map<String, String> orderquery(WXPay wxpay, String outTradeNo) throws Exception {
       Map<String, String> parament = new HashMap<String, String>();
   	parament.put("out_trade_no", outTradeNo);
   	return wxpay.orderQuery(parament);
   }
Copy the code

Welcome to pay attention to the public number oh ~