做電商平臺(tái)的小伙伴都知道,支付服務(wù)是必不可少的一部分,今天我們開(kāi)始就說(shuō)說(shuō)支付服務(wù)的接入及實(shí)現(xiàn)。目前在國(guó)內(nèi),幾乎90%中小公司的支付系統(tǒng)都離不開(kāi)微信支付和支付寶支付。那么大家要思考了,為什么微信支付和支付寶支付能作為大多數(shù)公司接入的首選呢?其實(shí)這個(gè)問(wèn)題大多小伙伴應(yīng)該是很清楚的,說(shuō)白了就是人家有龐大的用戶流量,目前微信在國(guó)內(nèi)的用戶已突破10億,支付寶也接近8億左右,如此龐大的用戶群體,你還會(huì)選擇其他的第三方支付(微博錢包、財(cái)付通、快錢等)嗎,作為普通客戶,大家都希望能方便快捷,誰(shuí)會(huì)為了在一個(gè)平臺(tái)買點(diǎn)東西下載或開(kāi)通其他服務(wù)呢,除非你給他有誘惑性的好處。今天我們先說(shuō)說(shuō)微信支付的接入及實(shí)現(xiàn)。
微信支付接入
首選我們?nèi)ノ⑿胖Ц兜墓倬W(wǎng),先看看官方提供的開(kāi)發(fā)文檔。鏈接地址:https://pay.weixin.qq.com/wiki/doc/api/index.html。
我們先看看微信支付目前提供的支付方式(如上圖),本次只講原生支付(掃碼支付)、App支付及小程序支付三種。
一,準(zhǔn)備工作
在開(kāi)發(fā)前,需要先申請(qǐng)一個(gè)商家版的微信公眾號(hào)或微信小程序(目前微信支付只有商家版公眾號(hào)可開(kāi)通),然后開(kāi)通微信支付功能,并做相應(yīng)的配置。
申請(qǐng)開(kāi)通微信公眾號(hào)和開(kāi)通微信支付(商戶)需要等待審核,一般都5個(gè)工作日左右。開(kāi)通成功后,需要獲取配置信息:
wx.pay.appid=*** wx.pay.mchid=*** wx.pay.key=*** wx.pay.secret=***
注:appid是公眾號(hào)ID,mchid是支付的商戶ID,
其中appid和secret可以在公眾平臺(tái)找著,mchid和key則在商戶平臺(tái)找到,特別是key(即API_KEY)要在商戶平臺(tái)設(shè)置好。本項(xiàng)目中這些配置通過(guò)properties文件放在***-payment-service工程的resource根路徑下。
在編碼之前,還需要登錄微信商戶平臺(tái)配置支付回調(diào)URL,此配置作為支付成功后回調(diào)接口的域名。如果配置的URL為:http://www.abc.com/, 你的支付回調(diào)路徑則可設(shè)置為:http://www.abc.com/api/payment/notify。
二,編碼階段
在開(kāi)始編碼前,我們必須先了解清楚微信支付的對(duì)接及支付的業(yè)務(wù)流程。
- 掃碼支付的業(yè)務(wù)流程:
App支付的業(yè)務(wù)流程:
小程序支付的業(yè)務(wù)流程:
從官方提供的業(yè)務(wù)流程圖我們可以大致總結(jié)對(duì)接流程如下:
1,在發(fā)起支付前,先在自己的商戶后臺(tái)下單,生成商戶訂單信息;
2,根據(jù)對(duì)應(yīng)支付方式的參數(shù)需求,封裝對(duì)應(yīng)所需參數(shù),并調(diào)用微信官方提供的統(tǒng)一下單Api接口下單;
3,統(tǒng)一下單成功,微信后臺(tái)返回對(duì)應(yīng)的響應(yīng)數(shù)據(jù)。返回?cái)?shù)據(jù)類型如下:
a,掃碼支付統(tǒng)一下單后會(huì)返回生成二維碼圖片的鏈接code_url;
b,app和小程序支付統(tǒng)一下單后會(huì)返回預(yù)支付id,即:prepay_id;
4,如果掃碼支付,你要用code_url生成一個(gè)二維碼展示在前端頁(yè)面供客戶掃碼付款;如果是app和小程序支付,后端只需將prepay_id及需要的參數(shù)傳給app和小程序端。app會(huì)通過(guò)調(diào)用SDK、小程序會(huì)通過(guò)調(diào)用微信的JS發(fā)起支付。
5,客戶付款成功后,客戶的微信端會(huì)展示付款結(jié)果信息,同時(shí)微信后臺(tái)會(huì)異步調(diào)用商戶后臺(tái)的回調(diào)接口(回調(diào)的api接口在統(tǒng)一下單作為下單參數(shù)),更新商戶系統(tǒng)的支付單狀態(tài)。
看到這里,大家會(huì)發(fā)現(xiàn)這三種方式的基本業(yè)務(wù)流程都差不多,只是由于不同支付方式調(diào)起微信應(yīng)用支付功能的方式不同,所以統(tǒng)一下單成功后返回的參數(shù)有所不同。
Controller接口層:
@RestController
@RequestMapping(value = "/api/payment/")
public class PaymentController {
private static Logger logger = LoggerFactory.getLogger(PaymentController.class);
@Autowired
private PaymentService paymentService;
/**
* App支付接口
* 微信和支付寶統(tǒng)一下單入口
*
* @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();
}
//驗(yàn)證訂單是否存在
String orderNo = jsonObj.getString("orderNo");
double payAmount = jsonObj.getDouble("payAmount");
if(payAmount < 0.01){
return AjaxUtil.renderFailMsg("訂單有誤,請(qǐng)確認(rèn)!");
} else {
//微信支付
Map<String, String> resMap = paymentService.wxAppPayment(orderInfo.getOrderNo(),orderInfo.getPayPrice(),null);
//判斷微信統(tǒng)一下單是否成功
if("SUCCESS".equals(resMap.get("returnCode")) && "OK".equals(resMap.get("returnMsg"))){
//統(tǒng)一下單成功
resMap.remove("returnCode");
resMap.remove("returnMsg");
logger.info("【App支付服務(wù)】微信支付下單成功!");
return AjaxUtil.renderSuccessMsg(resMap);
}else{
logger.info("【App支付服務(wù)】微信支付下單失敗!原因:"+resMap.get("returnMsg"));
return AjaxUtil.renderFailMsg(resMap.get("returnMsg"));
}
}
}
PaymentService接口方法:
PaymentService實(shí)現(xiàn)類部分代碼(微信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支付】 統(tǒng)一下單開(kāi)始, 訂單編號(hào)="+orderId);
SortedMap<String, String> resultMap = new TreeMap<String, String>();
//生成支付金額
double payAmount = PayUtils.getPayAmountByEnv(PROJECT_ENV, money);
//TODO 操作數(shù)據(jù)庫(kù),添加或更新支付記錄
//this.addOrUpdatePaymentRecord(......);
//微信統(tǒng)一下單
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"))){
//封裝參數(shù)返回
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支付】統(tǒng)一下單成功,返回參數(shù):"+resultMap);
}else{
resultMap.put("returnCode", resMap.get("return_code"));
resultMap.put("returnMsg", resMap.get("return_msg"));
LOGGER.info("【微信App支付】統(tǒng)一下單失敗,失敗原因:"+resMap.get("return_msg"));
}
return resultMap;
}
統(tǒng)一下單方法(在PaymentService實(shí)現(xiàn)類里):
/**
* <p>微信支付統(tǒng)一下單</p>
*
* @param orderId 訂單編號(hào)
* @param tradeType 支付類型
* @param payAmount 支付金額
* @param openid
* @return
* @throws Exception
*/
private Map<String,String> wxUnifieldOrder(String orderId, String tradeType, double payAmount, String openid) throws Exception{
//封裝參數(shù)
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));
//轉(zhuǎn)換為xml
String xmlData = PayUtils.mapToXml(paramMap);
//請(qǐng)求微信后臺(tái)
String resXml = HttpUtils.postData(PayConfig.WX_PAY_UNIFIED_ORDER, xmlData);
LOGGER.info("【微信支付】 統(tǒng)一下單響應(yīng):n"+resXml);
return PayUtils.xmlStrToMap(resXml);
}
統(tǒng)一下單完成,微信后臺(tái)將相應(yīng)的參數(shù)以xml的形式返回,統(tǒng)一下單成功后返回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>
因此我們需要將統(tǒng)一下單后的xml解析成map(上面的統(tǒng)一下單方法里已經(jīng)轉(zhuǎn)換成map),并判斷下單狀態(tài)。如果返回的return_code為SUCCESS并return_msg為OK,那么表示統(tǒng)一下單成功,然后封裝對(duì)應(yīng)的參數(shù)返回給前端。
前端根據(jù)下單成功后JAVA后端返回的參數(shù),進(jìn)行相應(yīng)的處理并喚起微信應(yīng)用的支付服務(wù)。注意,掃碼支付是用統(tǒng)一下單成功后微信后臺(tái)返回的code_url生成二維碼展示給客戶。二維碼的生成可以前端也可Java后端生成然后以輸出流的形式輸出到網(wǎng)頁(yè)上(堅(jiān)決不建議Java端生成二維碼圖片保存到文件服務(wù)器然后再展示)。
客戶在手機(jī)調(diào)起微信支付服務(wù)并輸入密碼成功付款后,客戶手機(jī)的微信里會(huì)收到支付成功的付款信息,同時(shí)微信后臺(tái)也在異步調(diào)用商戶的后臺(tái)接口。這個(gè)回調(diào)地址就是在統(tǒng)一下單方法里我們傳的notify_url字段的參數(shù)值。
下面是回調(diào)接口代碼:
/**
* 微信支付完成回調(diào)Api
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value="notify")
public void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
InputStream inputStream = request.getInputStream();
//獲取請(qǐng)求輸入流
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("【微信支付回調(diào)】 回調(diào)數(shù)據(jù):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)){
//更新支付單狀態(tài)信息
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[報(bào)文為空]></return_msg>" + "</xml>";
logger.info("支付失敗:"+resXml);
}
}else{
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[報(bào)文為空]></return_msg>" + "</xml>";
logger.info("【訂單支付失敗】");
}
logger.info("【微信支付回調(diào)響應(yīng)】 響應(yīng)內(nèi)容:n"+resXml);
//做出響應(yīng)
response.getWriter().print(resXml);
}
到此為止,所有的編碼工作已完成。
三,測(cè)試(用掃碼支付)
選擇要購(gòu)買的商品,然后下單,再去發(fā)起支付。
單擊“去支付”按鈕,跳轉(zhuǎn)到二維碼支付頁(yè)面:
掃碼支付完成后,顯示二維碼的頁(yè)面會(huì)跳轉(zhuǎn)到支付成功頁(yè)面(帶微信支付成功logo),并有3s的倒計(jì)時(shí),然后跳轉(zhuǎn)到“訂單詳情”頁(yè)。
學(xué)習(xí)更多的教程關(guān)注“重慶千鋒”公眾號(hào),獲取更多的學(xué)習(xí)教程。






