1. 程式人生 > >Java微信支付開發之掃碼支付模式一

Java微信支付開發之掃碼支付模式一

官方文件
準備工作:已通過微信認證的公眾號, 必須通過ICP備案域名(否則會報支付失敗)

借鑑了很多大神的文章,在此先謝過了

大體過程:先掃碼(還沒有確定實際要支付的金額),這個碼是商品的二維碼,再生成訂單,適用於自動販賣機之類固定金額的。

模式一支付的流程如下圖,稍微有點複雜

原生支付介面模式一時序圖

業務流程說明:

(1)商戶後臺系統根據微信支付規定格式生成二維碼(規則見下文),展示給使用者掃碼。
(2)使用者開啟微信“掃一掃”掃描二維碼,微信客戶端將掃碼內容傳送到微信支付系統。
(3)微信支付系統收到客戶端請求,發起對商戶後臺系統支付回撥URL的呼叫。呼叫請求將帶productid和使用者的openid等引數,並要求商戶系統返回交資料包

(4)商戶後臺系統收到微信支付系統的回撥請求,根據productid生成商戶系統的訂單。

(5)商戶系統呼叫微信支付【統一下單API】請求下單,獲取交易會話標識(prepay_id)。
(6)微信支付系統根據商戶系統的請求生成預支付交易,並返回交易會話標識(prepay_id)。
(7)商戶後臺系統得到交易會話標識prepay_id(2小時內有效)。
(8)商戶後臺系統將prepay_id返回給微信支付系統。
(9)微信支付系統根據交易會話標識,發起使用者端授權支付流程。
(10)使用者在微信客戶端輸入密碼,確認支付後,微信客戶端提交支付授權。
(11)微信支付系統驗證後扣款,完成支付交易。
(12)微信支付系統完成支付交易後給微信客戶端返回交易結果,並將交易結果通過簡訊、微信訊息提示使用者。微信客戶端展示支付交易結果頁面。
(13)微信支付系統通過傳送非同步訊息通知商戶後臺系統支付結果。商戶後臺系統需回覆接收情況,通知微信後臺系統不再發送該單的支付通知。
(14)未收到支付通知的情況,商戶後臺系統呼叫【查詢訂單API】。
(15)商戶確認訂單已支付後給使用者發貨。

一、設定回撥地址

商戶後臺系統根據微信支付規則連結生成二維碼,連結中帶固定引數productid(可定義為產品標識或訂單號)。使用者掃碼後,微信支付系統將productid和使用者唯一標識(openid)回撥商戶後臺系統(需要設定支付回撥URL),商戶後臺系統根據productid生成支付交易,最後微信支付系統發起使用者支付流程
商戶支付回撥URL設定指引:進入公眾平臺-->微信支付-->開發配置-->掃碼支付-->修改,如下圖所示。

這個支付回撥的URL設定的作用是接收使用者掃碼後微信支付系統傳送的資料,根據接收的資料生成商戶系統的支付訂單返回給微信支付系統,呼叫【統一下單API】提交支付交易。

掃碼支付引數設定欄目入口


二、生成微信支付二維碼

參考文件https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_6

二維碼長連結示例:

weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX

這樣太冗長了,轉換成短連結 weixin://wxpay/bizpayurl?pr=XXXX

/**
 * 掃碼支付模式一生成二維碼
 * 
 * @param productId 商品ID
 * @throws IOException
 */
@GetMapping("payone")
public Map<String, Object> payone(String productId) {
	Map<String, Object> data = new HashMap<>();
	String nonce_str = PayUtil.createNonceStr();
	// String product_id = "product_001"; // 推薦根據商品ID生成
	TreeMap<String, Object> packageParams = new TreeMap<>();
	packageParams.put("appid", WechatConfig.APP_ID);
	packageParams.put("mch_id", WechatConfig.MCH_ID);
	packageParams.put("product_id", productId);
	packageParams.put("time_stamp", PayUtil.createTimeStamp());
	packageParams.put("nonce_str", nonce_str);
	String str_url = PayUtil.createPayImageUrl(packageParams);
	String sign = SignatureUtil.createSign(packageParams, WechatConfig.API_KEY,
			SystemConfig.DEFAULT_CHARACTER_ENCODING);
	packageParams.put("sign", sign);
	String payurl = "weixin://wxpay/bizpayurl?sign=" + sign + str_url;
	logger.debug("payurl is {}", payurl);
	/**** 轉成短連結 ****/
	PayShortUrlParams payShortUrlParams = new PayShortUrlParams();
	payShortUrlParams.setAppid(WechatConfig.APP_ID);
	payShortUrlParams.setMch_id(WechatConfig.MCH_ID);
	payShortUrlParams.setLong_url(payurl);
	payShortUrlParams.setNonce_str(nonce_str);
	String urlSign = SignatureUtil.createSign(payShortUrlParams, WechatConfig.API_KEY,
			SystemConfig.DEFAULT_CHARACTER_ENCODING);
	payShortUrlParams.setSign(urlSign);
	String longXml = XmlUtil.toSplitXml(payShortUrlParams);
	String shortResult = HttpReqUtil.HttpsDefaultExecute(SystemConfig.POST_METHOD, WechatConfig.PAY_SHORT_URL, null,
			longXml);
	PayShortUrlResult payShortUrlResult = XmlUtil.getObjectFromXML(shortResult, PayShortUrlResult.class);
	if (Objects.equals("SUCCESS", payShortUrlResult.getReturn_code())) {
		payurl = payShortUrlResult.getShort_url();
	} else {
		logger.debug("錯誤資訊" + payShortUrlResult.getReturn_msg());
	}
	/**** 生成 二維碼圖片自行實現 ****/
	
	return data;
}
/**
 * 生成支付二維碼URL
 * 
 * @param params
 * @return
 */
public static String createPayImageUrl(TreeMap<String, Object> params) {
	StringBuffer buffer = new StringBuffer();
	for (Map.Entry<String, Object> entry : params.entrySet()) {
		if (entry.getValue() != null) {
			buffer.append("&" + entry.getKey() + "=" + entry.getValue());
		}
	}
	return buffer.toString();
}

二維碼生成

三、回撥商戶支付URL

接收使用者掃碼後微信支付系統傳送的資料,根據接收的資料生成商戶系統的支付訂單返回給微信支付系統,呼叫統一下單API提交支付交易

package com.phil.wechat.pay.controller;

import java.io.BufferedOutputStream;
import java.util.Objects;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.phil.modules.config.SystemConfig;
import com.phil.modules.config.WechatConfig;
import com.phil.modules.util.HttpReqUtil;
import com.phil.modules.util.PayUtil;
import com.phil.modules.util.SignatureUtil;
import com.phil.modules.util.XmlUtil;
import com.phil.wechat.base.controller.BaseController;
import com.phil.wechat.pay.model.rep.PayCallBackParams;
import com.phil.wechat.pay.model.rep.UnifiedOrderParams;
import com.phil.wechat.pay.model.resp.PayCallBackResult;
import com.phil.wechat.pay.model.resp.UnifiedOrderResult;

/**
 * 掃碼模式一回調
 * @author phil
 * @date 2017年6月27日
 */
@Controller
@RequestMapping("/wxpay/")
public class WechatPayCallBackController extends BaseController {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@RequestMapping("/callback") // 僅僅是掃碼模式一的
	public void callBack() throws Exception {
		String resXml = "";// 反饋給微信伺服器
		// 微信支付系統傳送的資料(<![CDATA[product_001]]>格式)
		String xml = HttpReqUtil.inputStreamToString(this.getRequest().getInputStream());
		// logger.info("微信支付系統傳送的資料"+xml);
		/**** 微信支付系統傳送的資料其實就是回撥地址輸入的引數Xml ****/
		// 驗證簽名
		if (SignatureUtil.checkIsSignValidFromWeiXin(xml, WechatConfig.API_KEY,
				SystemConfig.DEFAULT_CHARACTER_ENCODING)) {
			// 轉換成輸入引數,
			PayCallBackParams payCallBackParams = XmlUtil.getObjectFromXML(xml, PayCallBackParams.class);
			// appid openid mch_id is_subscribe nonce_str product_id sign
			// 統一下單
			String openid = payCallBackParams.getOpenid();
			String product_id = payCallBackParams.getProduct_id();
			/**** product_id 等 生成自己系統的訂單 ****/
			int total_fee = 1; // 根據product_id算出價格
			String out_trade_no = PayUtil.createOutTradeNo(); // 生成訂單號
			String body = product_id; // 商品名稱設定為product_id
			String attach = "XXX店"; // 附加資料
			String nonce_str = PayUtil.createNonceStr();
			String spbill_create_ip = HttpReqUtil.getRemortIP(this.getRequest());
			// 組裝統一下單的請求引數
			UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams();
			unifiedOrderParams.setAppid(WechatConfig.APP_ID);// 必須
			unifiedOrderParams.setMch_id(WechatConfig.MCH_ID);// 必須
			unifiedOrderParams.setOut_trade_no(out_trade_no);
			unifiedOrderParams.setBody(body);
			unifiedOrderParams.setAttach(attach);
			unifiedOrderParams.setTotal_fee(total_fee);// 必須
			unifiedOrderParams.setNonce_str(nonce_str); // 必須
			unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必須
			unifiedOrderParams.setTrade_type("NATIVE");// 必須
			unifiedOrderParams.setOpenid(openid);
			unifiedOrderParams.setNotify_url(WechatConfig.NOTIFY_URL); // 非同步通知URL
			// 簽名
			String sign = SignatureUtil.createSign(unifiedOrderParams, WechatConfig.API_KEY,
					SystemConfig.DEFAULT_CHARACTER_ENCODING);
			unifiedOrderParams.setSign(sign);
			// 統一下單 請求的Xml
			String unifiedXmL = XmlUtil.toSplitXml(unifiedOrderParams);
			// 統一下單 返回的xml
			String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(SystemConfig.POST_METHOD,
					WechatConfig.UNIFIED_ORDER_URL, null, unifiedXmL);
			// 統一下單返回 驗證簽名
			if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL, WechatConfig.API_KEY,
					SystemConfig.DEFAULT_CHARACTER_ENCODING)) {
				UnifiedOrderResult unifiedOrderResult = (UnifiedOrderResult) XmlUtil
						.getObjectFromXML(unifiedOrderResultXmL, UnifiedOrderResult.class);
				if (Objects.equals("SUCCESS", unifiedOrderResult.getReturn_code())
						&& Objects.equals("SUCCESS", unifiedOrderResult.getResult_code())) {
					PayCallBackResult payCallBackResult = new PayCallBackResult();
					payCallBackResult.setReturn_code(unifiedOrderResult.getReturn_code());
					payCallBackResult.setAppid(WechatConfig.APP_ID);
					payCallBackResult.setMch_id(WechatConfig.MCH_ID);
					payCallBackResult.setNonce_str(unifiedOrderResult.getNonce_str());// 直接用微信返回的
					/**** prepay_id 2小時內都有效,根據product_id再次支付方法自己寫 ****/
					payCallBackResult.setPrepay_id(unifiedOrderResult.getPrepay_id());
					payCallBackResult.setResult_code(unifiedOrderResult.getResult_code());
					String callsign = SignatureUtil.createSign(payCallBackResult, WechatConfig.API_KEY,
							SystemConfig.DEFAULT_CHARACTER_ENCODING);
					payCallBackResult.setSign(callsign);
					resXml = XmlUtil.toXml(payCallBackResult).replace("__", "_");
					// 將資料包返回給微信支付系統處理
				}
			} else {
				logger.info("簽名驗證錯誤");
				resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
						+ "<return_msg><![CDATA[簽名驗證錯誤]]></return_msg>" + "</xml> ";
			}
		} else {
			logger.info("簽名驗證錯誤");
			resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
					+ "<return_msg><![CDATA[簽名驗證錯誤]]></return_msg>" + "</xml> ";
		}
		BufferedOutputStream out = null;
		try {
			out = new BufferedOutputStream(this.getResponse().getOutputStream());
			out.write(resXml.getBytes());
			out.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(out);
		}
	}
}

四、完成支付並通知支付結果

關於原始碼,希望幫到了你