支付寶沙箱環(huán)境
螞蟻沙箱環(huán)境(Beta)是協(xié)助開發(fā)者進行接口功能開發(fā)及主要功能聯(lián)調的輔助環(huán)境。沙箱環(huán)境模擬了開放平臺部分產品的主要功能和主要邏輯(當前沙箱支持產品請參考“沙箱支持產品列表”)。在開發(fā)者應用上線審核前哨鸭,開發(fā)者可以根據(jù)自身需求里逆,先在沙箱環(huán)境中了解、組合和調試各種開放接口伐庭,進行開發(fā)調通工作粉渠,從而幫助開發(fā)者在應用上線審核完成后,能更快速圾另、更順利的進行線上調試和驗收工作霸株。如何使用和配置沙箱環(huán)境請參考《沙箱環(huán)境使用說明》。
注意:
由于沙箱為模擬環(huán)境集乔,在沙箱完成接口開發(fā)及主要功能調試后去件,請務必在螞蟻正式環(huán)境進行完整的功能驗收測試。所有返回碼及業(yè)務邏輯以正式環(huán)境為準。
為保證沙箱穩(wěn)定尤溜,沙箱環(huán)境測試數(shù)據(jù)會進行定期數(shù)據(jù)清理倔叼。Beta測試階段每周日中午12點至每周一中午12點為維護時間。在此時間內沙箱環(huán)境部分功能可能會不可用靴跛,敬請諒解缀雳。
請勿在沙箱進行壓力測試,以免觸發(fā)相應的限流措施梢睛,導致無法正常使用沙箱環(huán)境肥印。
沙箱支持的各個開放產品,沙箱使用的特別說明請參考各產品的快速接入文檔或技術接入文檔章節(jié)绝葡。
支付寶沙箱環(huán)境地址:支付寶沙箱環(huán)境
開發(fā)之前需要配置支付寶沙箱環(huán)境深碱,如果不會配置,請看博客:支付寶沙箱環(huán)境配置詳細步驟
支付寶支付流程
????????大體流程就是第三方平臺發(fā)起支付請求藏畅,傳入訂單信息敷硅,支付寶返回一個支付的form表單,第三方平臺渲染出這個表單愉阎,待客戶支付完成后绞蹦,支付寶會發(fā)起回調,一個是同步回調榜旦,是支付寶重定向的幽七,主要是提高用戶體驗,告訴客戶支付完畢溅呢,另一個是異步回調澡屡,支付寶通過http請求向第三方平臺發(fā)起支付回調,這個請求是讓第三方平臺更改訂單狀態(tài)咐旧,保證數(shù)據(jù)的一致性的驶鹉。支付完成異步回調支付寶需要第三方平臺必須打印"success",如果沒有打印"success",支付寶會有重試機制,支付寶服務器會不斷重發(fā)通知铣墨,直到超過24小時22分鐘室埋。一般情況下,25小時以內完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h)伊约,這就需要我們處理好冪等性問題姚淆,無論支付寶重試多少次,請求獲得的結果都一樣碱妆。如果不處理好冪等性問題肉盹,就會出現(xiàn)"本來支付10元換10個魚翅,結果換了20個30個魚翅"疹尾。常見的處理冪等性的方法是使用全局id+日志記錄的方式防止這種錯誤上忍。
支付寶支付流程
1骤肛、創(chuàng)建支付信息表
CREATE TABLE `payment_info` (
? `id` int(11) NOT NULL AUTO_INCREMENT,
? `userid` int(11) DEFAULT NULL COMMENT '客戶id',
? `typeid` int(2) DEFAULT NULL COMMENT '支付類型',
? `orderid` varchar(50) DEFAULT NULL COMMENT '訂單id',
? `price` decimal(10,0) DEFAULT NULL COMMENT '價格',
? `source` varchar(10) DEFAULT NULL COMMENT '支付信息來源',
? `state` int(2) DEFAULT NULL COMMENT '支付狀態(tài)',
? `created` datetime DEFAULT NULL,
? `updated` datetime DEFAULT NULL,
? `platformorderid` varchar(100) DEFAULT NULL COMMENT '支付寶第三方ID',
? `payMessage` varchar(10000) DEFAULT NULL COMMENT '支付信息',
? PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;
2、創(chuàng)建支付令牌
1)創(chuàng)建支付請求窍蓝,生成一條支付數(shù)據(jù)
2)生成支付令牌pay_token,存放在redis中腋颠,key為pay_token,value為支付id。
3)最后將pay_token返回
支付服務生成支付令牌代碼如下:
//生成支付令牌接口
@Override
public ResponseBasecreateToken(@RequestBody PaymentInfo paymentInfo){
//1吓笙、創(chuàng)建支付請求信息淑玫,生成一條支付數(shù)據(jù)
? ? Integer row =paymentInfoDao.savePaymentType(paymentInfo);
? ? if(row<=0){
????????return setResultError("創(chuàng)建支付訂單失敗");
? ? }
//2、生成對應的token
? ? String payToken = TokenUtils.getPayToken();
? ? //3面睛、存放在redis中絮蒿,key為:token,value為:支付id,過期時間15分鐘
? ? baseRedisService.setString(payToken,paymentInfo.getId()+"", Constants.PAY_TOKEN_TIME);
? ? //4叁鉴、返回token
? ? JSONObject result =new JSONObject();
? ? result.put("payToken",payToken);
? ? return setResultSuccess(result);
}
3土涝、使用支付令牌查找支付數(shù)據(jù),并發(fā)起支付請求幌墓,獲取支付寶返回的form表單數(shù)據(jù)
1)參數(shù)校驗但壮,判斷支付令牌pay_token是否失效或者已經(jīng)過期
2)使用支付令牌pay_token去redis中查詢出支付id
3)使用支付id去數(shù)據(jù)庫中查詢出支付信息。
4)利用支付信息下單常侣,調用支付寶api,獲取網(wǎng)頁支付的form表單
5)最后返回form表單數(shù)據(jù)
支付服務根據(jù)支付令牌獲取form表單代碼如下:
/**
* 使用支付令牌查找支付信息生成網(wǎng)頁支付的form表單
* @param payToken
* @return
*/
@Override
public ResponseBasealiPagePay(@RequestParam("payToken") String payToken){
//1蜡饵、校驗參數(shù)
? ? if(StringUtils.isEmpty(payToken)){
????????return setResultError("token不能為空");
? ? }
//2、判斷token有效期
? ? String payId = (String)baseRedisService.getString(payToken);
? ? if(StringUtils.isEmpty(payId)){
return setResultError("支付已經(jīng)超時");
? ? }
//4胳施、使用支付id,進行下單
? ? //5溯祸、使用支付id查詢支付信息
? ? PaymentInfo paymentInfo =paymentInfoDao.getPaymentInfo(Long.parseLong(payId));
? ? if(paymentInfo==null){
return setResultError("未找到支付信息");
? ? }
//6、對接支付代碼,返回提交支付的form表單元素的token
? ? //獲得初始化的AlipayClient
? ? //獲得初始化的AlipayClient
? ? AlipayClient alipayClient =new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
? ? //設置請求參數(shù)(網(wǎng)頁端登錄)
? ? AlipayTradePagePayRequest alipayRequest =new AlipayTradePagePayRequest();
? ? //設置同步回調地址
? ? alipayRequest.setReturnUrl(AlipayConfig.return_url);
? ? //設置異步回調地址
? ? alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
? ? //商戶訂單號巾乳,商戶網(wǎng)站訂單系統(tǒng)中唯一訂單號您没,必填
? ? Stringout_trade_no= paymentInfo.getOrderId();
? ? //付款金額鸟召,必填,企業(yè)金額胆绊,單位:分
? ? Stringtotal_amount= paymentInfo.getPrice()+"";
? ? //訂單名稱,必填
? ? String subject ="劉德煌會員";
? ? alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
? ? ? ? ? ? +"\"total_amount\":\""+ total_amount +"\","
? ? ? ? ? ? +"\"subject\":\""+ subject +"\","
? ? ? ? ? ? //+ "\"body\":\""+ body +"\","
? ? ? ? ? ? +"\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
? ? //若想給BizContent增加其他可選請求參數(shù)欧募,以增加自定義超時時間參數(shù)timeout_express來舉例說明
? ? //alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
//? ? + "\"total_amount\":\""+ total_amount +"\","
//? ? + "\"subject\":\""+ subject +"\","
//? ? + "\"body\":\""+ body +"\","
//? ? + "\"timeout_express\":\"10m\","
//? ? + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
? ? //請求參數(shù)可查閱【電腦網(wǎng)站支付的API文檔-alipay.trade.page.pay-請求參數(shù)】章節(jié)
? ? //請求
? ? try{
String result = alipayClient.pageExecute(alipayRequest).getBody();
? ? ? ? AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest);
? ? ? ? log.info("body:{}",response.getBody());
? ? ? ? JSONObject data =new JSONObject();
? ? ? ? data.put("payHtml",result);
? ? ? ? return setResultSuccess(data);
? ? }catch (Exception e){
return setResultError("支付異常");
? ? }
}
pc-web端控制層調用支付服務流程:
pc-web控制層調用支付服務流程:
1压状、獲取前端傳入的支付令牌,驗證支付令牌參數(shù)的正確性
2跟继、調用支付服務根據(jù)支付令牌獲取支付form表單元素接口种冬,獲取支付form表單
3、在頁面上渲染出支付form表單元素
//使用payToken進行支付
@RequestMapping("/aliPay")
public void aliPay(String payToken, HttpServletResponse response)throws IOException {
response.setContentType("text/html;charset=utf-8");
? ? PrintWriter writer = response.getWriter();
? ? //1舔糖、參數(shù)驗證
? ? if(StringUtils.isEmpty(payToken)){
return;
? ? }
//2娱两、調用支付服務接口,獲取支付寶html元素
? ? ResponseBase payTokenResult =payServiceFeign.aliPagePay(payToken);
? ? if(!payTokenResult.getCode().equals(Constants.HTTP_RES_CODE_200)){
String message = payTokenResult.getMsg();
? ? ? ? writer.print(message);
return;
? ? }
//3金吗、返回可以執(zhí)行的html元素給客戶端
? ? LinkedHashMap map = (LinkedHashMap) payTokenResult.getData();
? ? String payHtml = (String)map.get("payHtml");
? ? log.info("payHtml:{}",payHtml);
? ? //4十兢、頁面上渲染出form表單
? ? writer.write(payHtml);
? ? writer.close();
}
待客戶支付完畢后趣竣,支付寶會發(fā)起兩個回調,一個是同步回調旱物,一個是異步回調遥缕。同步回調是支付寶重定向到第三方事先填好的同步回調地址,告訴客戶支付完畢宵呛,異步回調則是支付寶發(fā)起http請求給第三方平臺单匣,讓第三方平臺更改支付狀態(tài)以及訂單狀態(tài)。
支付寶同步回調流程
pc-web端控制層同步回調控制層synCallBack方法流程:
1宝穗、接收到支付寶同步回調的參數(shù)后户秤,封裝成map
2、調用支付服務的同步回調接口逮矛,告訴客戶支付完成虎忌。
注意:支付寶同步回調時通過重定向的方式回調到第三方平臺,如果數(shù)據(jù)不加以處理橱鹏,同步回調的參數(shù)會顯示在地址欄中膜蠢,而這些參數(shù)中又有比較重要的信息如:商戶訂單號、支付寶交易號等莉兰。為避免這些數(shù)據(jù)在地址欄出現(xiàn)挑围,我們可以把這些參數(shù)封裝成form表單,瀏覽器模擬提交form表單請求糖荒,以post請求提交表單杉辙,隱藏同步回調的參數(shù)。
pc-web端控制層同步回調方法代碼如下:
/**
* 同步通知
*/
@RequestMapping("/synCallBack")
public void synCallBack(HttpServletRequest request, HttpServletResponse response)throws IOException {
response.setContentType("text/html;charset=utf-8");
? ? Map params =new HashMap();
? ? Map requestParams = request.getParameterMap();
? ? for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
? ? ? ? String[] values = (String[]) requestParams.get(name);
? ? ? ? String valueStr ="";
? ? ? ? for (int i =0; i < values.length; i++) {
valueStr = (i == values.length -1) ? valueStr + values[i]
: valueStr + values[i] +",";
? ? ? ? }
//亂碼解決捶朵,這段代碼在出現(xiàn)亂碼時使用
? ? ? ? valueStr =new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
? ? ? ? params.put(name, valueStr);
? ? }
log.info("#####支付寶同步回調CallBackController#####synCallBack開始params:{}",params);
? ? PrintWriter writer = response.getWriter();
? ? ResponseBase synCallBackResponseBase =callBackServiceFeign.synCallBack(params);
? ? if(!synCallBackResponseBase.getCode().equals(Constants.HTTP_RES_CODE_200)){
//保錯頁面
? ? ? ? return;
? ? }
LinkedHashMap data = (LinkedHashMap) synCallBackResponseBase.getData();
? ? //封裝成html的form表單蜘矢,瀏覽器模擬提交(為了避免支付寶回調地址上的get請求參數(shù)顯示在地址欄,因此模擬表單請求综看,隱藏同步回調的參數(shù))
? ? String htmlFrom ="
? ? ? ? ? ? +" method='post' action='http://127.0.0.1/alibaba/callBack/synSuccessPage' >"
? ? ? ? ? ? +"<input type='hidden' name='outTradeNo' value='" + data.get("outTradeNo") +"'>"
? ? ? ? ? ? +"<input type='hidden' name='tradeNo' value='" + data.get("tradeNo") +"'>"
? ? ? ? ? ? +"<input type='hidden' name='totalAmount' value='" + data.get("totalAmount") +"'>"
? ? ? ? ? ? +"<input type='submit' value='立即支付' style='display:none'>"
? ? ? ? ? ? +"</form><script>document.forms[0].submit();" +"</script>";
? ? writer.println(htmlFrom);
? ? writer.close();
? ? log.info("#####支付寶同步回調CallBackController#####synCallBack結束params:{}",params);
}
pc-web端控制層以post請求提交表單后處理請求的方法如下品腹。
//以post請求隱藏參數(shù)
@RequestMapping(method = RequestMethod.POST,value ="/synSuccessPage")
public StringsynSuccessPage(HttpServletRequest request,String outTradeNo,String tradeNo,String totalAmount){
request.setAttribute("outTradeNo",outTradeNo);
? ? request.setAttribute("tradeNo",tradeNo);
? ? request.setAttribute("totalAmount",totalAmount);
? ? return PAY_SUCCESS;
}
支付服務同步回調接口流程:
1、對pc-web端傳入的參數(shù)進行驗簽红碑。
2舞吭、驗簽成功返回訂單相關信息的json給pc-web控制層,驗簽失敗析珊,返回驗簽失敗
支付服務同步回調代碼如下:
@Override
public ResponseBasesynCallBack(@RequestParam Map params) {
//1羡鸥、日志記錄
? ? log.info("#####支付寶同步通知synCallBack開始,params:{}",params);
? ? //2、驗簽操作
? ? try{
//調用SDK驗證簽名
? ? ? ? boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
? ? ? ? log.info("#####支付寶同步通知signVerified:{}",signVerified);
? ? ? ? //——請在這里編寫您的程序(以下代碼僅作參考)——
? ? ? ? if(!signVerified) {
return setResultError("驗簽失敗");
? ? ? ? }
//商戶訂單號
? ? ? ? String outTradeNo = params.get("out_trade_no");
? ? ? ? //支付寶交易號
? ? ? ? String tradeNo = params.get("trade_no");
? ? ? ? //付款金額
? ? ? ? String totalAmount = params.get("total_amount");
? ? ? ? JSONObject json =new JSONObject();
? ? ? ? json.put("outTradeNo",outTradeNo);
? ? ? ? json.put("tradeNo",tradeNo);
? ? ? ? json.put("totalAmount",totalAmount);
? ? ? ? return setResultSuccess(json);
? ? }catch (Exception e){
log.info("#####支付寶同步通知出現(xiàn)異常,ERROR:",e);
? ? ? ? return setResultError("系統(tǒng)錯誤");
? ? }finally {
log.info("#####支付寶同步通知synCallBack結束,params:{}",params);
? ? }
}
支付寶異步回調流程
支付寶異步回調需要第三方平臺輸出打印success才算成功忠寻,否則支付寶會不斷的重試惧浴,這就需要處理號冪等性問題。
pc-web端控制層異步回調方法流程:
1奕剃、接收到支付寶發(fā)送過來的異步回調http請求衷旅,把參數(shù)封裝成map
2哑姚、調用支付服務異步回調接口
3、最后返回結果芜茵。
pc-web端控制層異步回調方法代碼如下:
/**
? ? * 異步通知
? ? * @param request
? ? * @param response
? ? * @return
? ? */
? ? @RequestMapping("/asynCallBack")
@ResponseBody
? ? public StringasynCallBack(HttpServletRequest request, HttpServletResponse response){
Map params =new HashMap();
? ? ? ? Map requestParams = request.getParameterMap();
? ? ? ? for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
? ? ? ? ? ? String[] values = (String[]) requestParams.get(name);
? ? ? ? ? ? String valueStr ="";
? ? ? ? ? ? for (int i =0; i < values.length; i++) {
valueStr = (i == values.length -1) ? valueStr + values[i]
: valueStr + values[i] +",";
? ? ? ? ? ? }
/*? ? ? ? ? ? //亂碼解決叙量,這段代碼在出現(xiàn)亂碼時使用valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");*/
? ? ? ? ? ? params.put(name, valueStr);
? ? ? ? }
log.info("#####支付寶同步回調CallBackController#####synCallBack開始params:{}",params);
? ? ? ? String result =callBackServiceFeign.asynCallBack(params);
? ? ? ? return result;
? ? }
支付服務異步回調接口流程:
1、對pc-web傳入的參數(shù)進行驗簽
2九串、根據(jù)參數(shù)中的訂單號查詢支付信息绞佩。
3、利用訂單號作為全局id猪钮,解決冪等性問題品山,判斷支付信息中的支付狀態(tài),如果是支付成功烤低,則直接返回success肘交,防止支付寶重試帶來的冪等性問題。
4扑馁、修改支付信息表的支付狀態(tài)涯呻。
5、調用訂單服務腻要,通知訂單服務更改訂單狀態(tài)(注意:這里需要注意如果訂單服務調用失敗复罐,注意事務回滾)
支付服務異步回調接口代碼如下:
/**
* 異步通知
* @param params
* @return
*/
@LcnTransaction
@Transactional
@Override
public synchronized StringasynCallBack(@RequestParam Map params) {
//1、日志記錄
? ? log.info("#####支付寶異步通知synCallBack開始,params:{}",params);
? ? //2雄家、驗簽操作
? ? try{
//調用SDK驗證簽名
? ? ? ? boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
? ? ? ? log.info("#####支付寶異步通知signVerified:{}",signVerified);
? ? ? ? //——請在這里編寫您的程序(以下代碼僅作參考)——
? ? ? ? if(!signVerified) {
return Constants.PAY_FAIL;
? ? ? ? }
//商戶訂單號
? ? ? ? String outTradeNo = params.get("out_trade_no");
? ? ? ? //修改支付數(shù)據(jù)庫
? ? ? ? //使用訂單號作為全局id解決冪等性問題效诅,如果有網(wǎng)絡延遲,需要采用分布式鎖
? ? ? ? PaymentInfo payInfo =paymentInfoDao.getByOrderIdPayInfo(outTradeNo);
? ? ? ? if(payInfo==null){
return Constants.PAY_FAIL;
? ? ? ? }
//支付寶重試機制
? ? ? ? Integer state = payInfo.getState();
? ? ? ? if(state ==1){
return Constants.PAY_SUCCESS;
? ? ? ? }
//支付寶交易號
? ? ? ? String tradeNo = params.get("trade_no");
? ? ? ? //付款金額
? ? ? ? String totalAmount = params.get("total_amount");
? ? ? ? //判斷實際付款金額與商品金額是否一致
? ? ? /* if(!totalAmount.equals(String.valueOf(payInfo.getPrice()))){
return Constants.PAY_FAIL;
}*/
? ? ? ? //標識為該訂單已支付
? ? ? ? payInfo.setState(1);
? ? ? ? //支付寶返回的參數(shù)
? ? ? ? payInfo.setPayMessage(params.toString());
? ? ? ? //設置第三方交易id
? ? ? ? payInfo.setPlatformorderId(tradeNo);
? ? ? ? //手動事務開啟
? ? ? ? int row =paymentInfoDao.updatePayInfo(payInfo);
? ? ? ? if(row <=0){
????????????return Constants.PAY_FAIL;
? ? ? ? }
//調用訂單數(shù)據(jù)庫通知支付狀態(tài)
? ? ? ? ResponseBase responseBase =orderServiceFeign.updateOrder(1L, tradeNo, outTradeNo);
? ? ? ? if(!responseBase.getCode().equals(Constants.HTTP_RES_CODE_200)){
//回滾 手動回滾事務
? ? ? ? ? ? TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
? ? ? ? ? ? return Constants.PAY_FAIL;
? ? ? ? }
//手動事務提交
? ? ? ? return Constants.PAY_SUCCESS;
? ? }catch (Exception e){
log.info("#####支付寶異步通知出現(xiàn)異常,ERROR:",e);
? ? ? ? TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
? ? ? ? return Constants.PAY_FAIL;
? ? }finally {
log.info("#####支付寶異步通知synCallBack結束,params:{}",params);
? ? }
}
注意:在這個異步回調的過程中趟济,如果不注意乱投,假如在通知訂單修改訂單狀態(tài)的后面出現(xiàn)代碼異常,由于由事務的存在顷编,本地的支付數(shù)據(jù)庫回滾戚炫,當前支付狀態(tài)回滾為未支付,但訂單服務那邊的訂單狀態(tài)已經(jīng)修改成功勾效,所以訂單服務的支付狀態(tài)為已支付嘹悼,這是典型的分布式事務場景叛甫。在這里我是采用的LCN分布式事務解決框架解決的层宫。在后面的筆記中,我會寫一些關于分布式事務解決方案的文章其监。
支付寶關于異步回調的說明:支付寶異步回調說明