做電商平臺的小伙伴都知道,支付服務是必不可少的一部分,今天我們開始就說說支付服務的接入及實現。目前在國內,幾乎90%中小公司的支付系統都離不開微信支付和支付寶支付。那么大家要思考了,為什么微信支付和支付寶支付能作為大多數公司接入的首選呢?其實這個問題大多小伙伴應該是很清楚的,說白了就是人家有龐大的用戶流量,目前微信在國內的用戶已突破10億,支付寶也接近8億左右,如此龐大的用戶群體,你還會選擇其他的第三方支付(微博錢包、財付通、快錢等)嗎,作為普通客戶,大家都希望能方便快捷,誰會為了在一個平臺買點東西下載或開通其他服務呢,除非你給他有誘惑性的好處。今天我們先說說微信支付的接入及實現。
微信支付接入
首選我們去微信支付的官網,先看看官方提供的開發文檔。鏈接地址:https://pay.weixin.qq.com/wiki/doc/api/index.html。
我們先看看微信支付目前提供的支付方式(如上圖),本次只講原生支付(掃碼支付)、App支付及小程序支付三種。
一,準備工作
在開發前,需要先申請一個商家版的微信公眾號或微信小程序(目前微信支付只有商家版公眾號可開通),然后開通微信支付功能,并做相應的配置。
申請開通微信公眾號和開通微信支付(商戶)需要等待審核,一般都5個工作日左右。開通成功后,需要獲取配置信息:
wx.pay.appid=*** wx.pay.mchid=*** wx.pay.key=*** wx.pay.secret=***
注:appid是公眾號ID,mchid是支付的商戶ID,
其中appid和secret可以在公眾平臺找著,mchid和key則在商戶平臺找到,特別是key(即API_KEY)要在商戶平臺設置好。本項目中這些配置通過properties文件放在***-payment-service工程的resource根路徑下。
在編碼之前,還需要登錄微信商戶平臺配置支付回調URL,此配置作為支付成功后回調接口的域名。如果配置的URL為:http://www.abc.com/, 你的支付回調路徑則可設置為:http://www.abc.com/api/payment/notify。
二,編碼階段
在開始編碼前,我們必須先了解清楚微信支付的對接及支付的業務流程。
- 掃碼支付的業務流程:
App支付的業務流程:
小程序支付的業務流程:
從官方提供的業務流程圖我們可以大致總結對接流程如下:
1,在發起支付前,先在自己的商戶后臺下單,生成商戶訂單信息;
2,根據對應支付方式的參數需求,封裝對應所需參數,并調用微信官方提供的統一下單Api接口下單;
3,統一下單成功,微信后臺返回對應的響應數據。返回數據類型如下:
a,掃碼支付統一下單后會返回生成二維碼圖片的鏈接code_url;
b,app和小程序支付統一下單后會返回預支付id,即:prepay_id;
4,如果掃碼支付,你要用code_url生成一個二維碼展示在前端頁面供客戶掃碼付款;如果是app和小程序支付,后端只需將prepay_id及需要的參數傳給app和小程序端。app會通過調用SDK、小程序會通過調用微信的JS發起支付。
5,客戶付款成功后,客戶的微信端會展示付款結果信息,同時微信后臺會異步調用商戶后臺的回調接口(回調的api接口在統一下單作為下單參數),更新商戶系統的支付單狀態。
看到這里,大家會發現這三種方式的基本業務流程都差不多,只是由于不同支付方式調起微信應用支付功能的方式不同,所以統一下單成功后返回的參數有所不同。
Controller接口層:
@RestController @RequestMapping(value = "/api/payment/") public class PaymentController { private static Logger logger = LoggerFactory.getLogger(PaymentController.class); @Autowired private PaymentService paymentService; /** * App支付接口 * 微信和支付寶統一下單入口 * * @param request * @return * @throws Exception */ @ResponseBody @RequestMapping(value="toPay", method=RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) public JSONObject toPay(HttpServletRequest request) throws Exception { String requestStr = RequestStr.getRequestStr(request); if (StringUtils.isEmpty(requestStr)) { throw new ParamException(); } JSONObject jsonObj = JSONObject.parseobject(requestStr); if(StringUtils.isEmpty(jsonObj.getString("orderNo")) || StringUtils.isEmpty(jsonObj.getString("payAmount"))){ throw new ParamException(); } //驗證訂單是否存在 String orderNo = jsonObj.getString("orderNo"); double payAmount = jsonObj.getDouble("payAmount"); if(payAmount < 0.01){ return AjaxUtil.renderFailMsg("訂單有誤,請確認!"); } else { //微信支付 Map<String, String> resMap = paymentService.wxAppPayment(orderInfo.getOrderNo(),orderInfo.getPayPrice(),null); //判斷微信統一下單是否成功 if("SUCCESS".equals(resMap.get("returnCode")) && "OK".equals(resMap.get("returnMsg"))){ //統一下單成功 resMap.remove("returnCode"); resMap.remove("returnMsg"); logger.info("【App支付服務】微信支付下單成功!"); return AjaxUtil.renderSuccessMsg(resMap); }else{ logger.info("【App支付服務】微信支付下單失敗!原因:"+resMap.get("returnMsg")); return AjaxUtil.renderFailMsg(resMap.get("returnMsg")); } } }
PaymentService接口方法:
PaymentService實現類部分代碼(微信App支付):
@Service(value = "paymentService") public class PaymentServiceImpl implements PaymentService { private static Logger LOGGER = LoggerFactory.getLogger(PaymentServiceImpl.class); @Value("${spring.profiles.active}") private String PROJECT_ENV; @Value("${hcc.pay.domain}") private String payDomain; @Autowired private PaymentRecordMapper paymentRecordMapper; @Override @Transactional(readOnly=false,rollbackFor={Exception.class}) public Map<String,String> wxAppPayment(String orderId, double money,Long customerId) throws Exception { LOGGER.info("【微信App支付】 統一下單開始, 訂單編號="+orderId); SortedMap<String, String> resultMap = new TreeMap<String, String>(); //生成支付金額 double payAmount = PayUtils.getPayAmountByEnv(PROJECT_ENV, money); //TODO 操作數據庫,添加或更新支付記錄 //this.addOrUpdatePaymentRecord(......); //微信統一下單 Map<String,String> resMap = this.wxUnifieldOrder(orderId, PayConfig.TRADE_TYPE_APP, payAmount, null); if(PayConstant.SUCCESS.equals(resMap.get("return_code")) && PayConstant.OK.equals(resMap.get("return_msg"))){ //封裝參數返回 resultMap.put("appid", PayConfig.WX_APP_ID); resultMap.put("partnerid", PayConfig.WX_MCH_ID); resultMap.put("prepayid", resMap.get("prepay_id")); resultMap.put("package", "Sign=WXPay"); resultMap.put("noncestr", PayUtils.makeUUID(32)); resultMap.put("timestamp", PayUtils.getCurrentTimeStamp()); resultMap.put("sign", PayUtils.createSign(resultMap,PayConfig.WX_KEY)); resultMap.put("returnCode", "SUCCESS"); resultMap.put("returnMsg", "OK"); LOGGER.info("【微信App支付】統一下單成功,返回參數:"+resultMap); }else{ resultMap.put("returnCode", resMap.get("return_code")); resultMap.put("returnMsg", resMap.get("return_msg")); LOGGER.info("【微信App支付】統一下單失敗,失敗原因:"+resMap.get("return_msg")); } return resultMap; }
統一下單方法(在PaymentService實現類里):
/** * <p>微信支付統一下單</p> * * @param orderId 訂單編號 * @param tradeType 支付類型 * @param payAmount 支付金額 * @param openid * @return * @throws Exception */ private Map<String,String> wxUnifieldOrder(String orderId, String tradeType, double payAmount, String openid) throws Exception{ //封裝參數 SortedMap<String,String> paramMap = new TreeMap<String,String>(); String appid = PayConfig.WX_APP_ID; String mchid = PayConfig.WX_MCH_ID; if(PayConstant.WX_TRADE_TYPE_JSAPI.equals(tradeType)){ appid = PayConfig.XCX_APP_ID; mchid = PayConfig.XCX_MCH_ID; } paramMap.put("appid", appid); paramMap.put("mch_id", mchid); paramMap.put("nonce_str", PayUtils.makeUUID(32)); paramMap.put("body", BaseConstants.PLATFORM_COMPANY_NAME); paramMap.put("out_trade_no", orderId); paramMap.put("total_fee", PayUtils.moneyToIntegerStr(payAmount)); paramMap.put("spbill_create_ip", PayUtils.getLocalIp()); paramMap.put("notify_url", this.getNotifyUrl(PayConstant.PAY_TYPE_WX)); paramMap.put("trade_type", tradeType); if(PayConstant.WX_TRADE_TYPE_JSAPI.equals(tradeType)){ paramMap.put("openid",openid); } paramMap.put("sign", PayUtils.createSign(paramMap,PayConfig.WX_KEY)); //轉換為xml String xmlData = PayUtils.mapToXml(paramMap); //請求微信后臺 String resXml = HttpUtils.postData(PayConfig.WX_PAY_UNIFIED_ORDER, xmlData); LOGGER.info("【微信支付】 統一下單響應:n"+resXml); return PayUtils.xmlStrToMap(resXml); }
統一下單完成,微信后臺將相應的參數以xml的形式返回,統一下單成功后返回xml示例:
<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wx2421b1c4370ec43b]]></appid> <mch_id><![CDATA[10000100]]></mch_id> <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str> <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id> <trade_type><![CDATA[APP]]></trade_type> </xml>
因此我們需要將統一下單后的xml解析成map(上面的統一下單方法里已經轉換成map),并判斷下單狀態。如果返回的return_code為SUCCESS并return_msg為OK,那么表示統一下單成功,然后封裝對應的參數返回給前端。
前端根據下單成功后JAVA后端返回的參數,進行相應的處理并喚起微信應用的支付服務。注意,掃碼支付是用統一下單成功后微信后臺返回的code_url生成二維碼展示給客戶。二維碼的生成可以前端也可Java后端生成然后以輸出流的形式輸出到網頁上(堅決不建議Java端生成二維碼圖片保存到文件服務器然后再展示)。
客戶在手機調起微信支付服務并輸入密碼成功付款后,客戶手機的微信里會收到支付成功的付款信息,同時微信后臺也在異步調用商戶的后臺接口。這個回調地址就是在統一下單方法里我們傳的notify_url字段的參數值。
下面是回調接口代碼:
/** * 微信支付完成回調Api * * @param request * @param response * @throws Exception */ @RequestMapping(value="notify") public void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception { InputStream inputStream = request.getInputStream(); //獲取請求輸入流 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len=inputStream.read(buffer))!=-1){ outputStream.write(buffer,0,len); } outputStream.close(); inputStream.close(); Map<String,Object> map = BeanToMap.getMapFromXML(new String(outputStream.toByteArray(),"utf-8")); logger.info("【微信支付回調】 回調數據:n"+map); String resXml = ""; String returnCode = (String) map.get("return_code"); if ("SUCCESS".equalsIgnoreCase(returnCode)) { String returnmsg = (String) map.get("result_code"); if("SUCCESS".equals(returnmsg)){ //更新支付單狀態信息 int result = paymentService.wxNotify(map); if(result > 0){ //支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>"+"</xml>"; } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]></return_msg>" + "</xml>"; logger.info("支付失敗:"+resXml); } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]></return_msg>" + "</xml>"; logger.info("【訂單支付失敗】"); } logger.info("【微信支付回調響應】 響應內容:n"+resXml); //做出響應 response.getWriter().print(resXml); }
到此為止,所有的編碼工作已完成。
三,測試(用掃碼支付)
選擇要購買的商品,然后下單,再去發起支付。
單擊“去支付”按鈕,跳轉到二維碼支付頁面:
掃碼支付完成后,顯示二維碼的頁面會跳轉到支付成功頁面(帶微信支付成功logo),并有3s的倒計時,然后跳轉到“訂單詳情”頁。
學習更多的教程關注“重慶千鋒”公眾號,獲取更多的學習教程。