Android支付寶支付設(shè)計(jì)開(kāi)發(fā)
在移動(dòng)支付領(lǐng)域,支付寶支付占用巨大份額,根據(jù)艾瑞咨詢公布的報(bào)告數(shù)據(jù):2014Q3,支付寶斬獲了82.6%的市場(chǎng)份額,在移動(dòng)支付的霸主地位越來(lái)越穩(wěn)固。財(cái)付通支付的發(fā)力點(diǎn)在微信支付和手Q支付,在移動(dòng)支付格局中取得了10.0%的市場(chǎng)份額,排名第二。
支付寶在移動(dòng)支付領(lǐng)域的統(tǒng)治地位,使得我們有必要梳理支付寶移動(dòng)開(kāi)發(fā)流程。本文寫作的目的就是梳理支付流程,從架構(gòu)層面講述如何在移動(dòng)應(yīng)用中嵌入支付寶支付功能,以及指出哪些地方存在開(kāi)發(fā)陷阱。
準(zhǔn)備
按照說(shuō)明,首先需要申請(qǐng)支付寶支付賬號(hào)。這方面根據(jù)網(wǎng)站說(shuō)明進(jìn)行申請(qǐng)即可。一般需要2周左右的時(shí)間批準(zhǔn)下來(lái)。
申請(qǐng)成功后賬號(hào)信息包括 合作者身份ID partner, 賣家支付寶賬號(hào) seller_id,以及私鑰 privateKey等。這三項(xiàng)將用于開(kāi)發(fā)過(guò)程。
在官網(wǎng)上下載移動(dòng)支付集成開(kāi)發(fā)包。解壓后, 發(fā)現(xiàn)其下包括三個(gè)文件夾(在英文Mac系統(tǒng)下文件名顯示為亂碼):
- “商戶接入支付寶收銀臺(tái)界面展示標(biāo)準(zhǔn)”:講的是如何使用支付寶Logo。
- “支付寶錢包支付接口開(kāi)發(fā)包2.0標(biāo)準(zhǔn)版”:用于支付,包括客戶端和服務(wù)器端開(kāi)發(fā)。
- “即時(shí)到賬批量退款有密接口refund_fastpay_by_platform_pwd”:用于到賬及批量退款,只需要服務(wù)器端操作處理。
后兩個(gè)文件夾,都包括4方面內(nèi)容:接口文檔,接入與使用規(guī)則,demo代碼,以及版本更新說(shuō)明。
架構(gòu)設(shè)計(jì)
首先,對(duì)于一個(gè)實(shí)際的App應(yīng)用而言,可能會(huì)包括多種支付方式,因此可以采用設(shè)計(jì)模式中的策略Strategy模式來(lái)設(shè)計(jì)支付功能模塊,支付寶支付作為其中的一個(gè)策略,pay方法是支付算法。
如果除了支付方式payment method變化,訂單order也可能會(huì)有不同的形式,如格式可能不同,有些支持可退款,有的不允許退款等,在這種多維度可變的情況下,支付模塊的架構(gòu)可以基于橋接模式。
其次,可以把支付寶支付的各個(gè)操作步驟,比如獲取訂單號(hào),生成訂單數(shù)據(jù),進(jìn)行支付,獲取支付結(jié)果,處理異常等操作,根據(jù)狀態(tài)進(jìn)行劃分。這樣采用狀態(tài)模式,提供設(shè)計(jì)的靈活性和擴(kuò)展性。另外也可以設(shè)計(jì)狀態(tài)機(jī)進(jìn)行統(tǒng)一的狀態(tài)切換管理。下面為參考代碼:
public class PayStateMachine {
/* all possible state of payment */
public enum PayState { PAY_INIT, PAY_GOT_CONTEXT, PAY_UPDATED_ORDER, PAY_APPLIED_
ID, PAY_ORDER_CREATED, PAY_SUCCEED, ERROR_OCCURRED}
/* errors may occurred during payment */
public enum PayError {
PAY_GET_CONTEXT_FAIL, PAY_UPDATE_ORDER_FAIL, PAY_APPLY_ID_FAIL, PAY_FAIL
}
private static PayStateMachine instance;
private PayState state;
private IOrder order;
private IPayment payment;
private PayStateMachine() {
}
public static PayStateMachine getInstance() {
if (instance == null) {
instance = new PayStateMachine();
}
return instance;
}
public void initPayment(IOrder order, IPayment payment) {
this.order = order;
this.payment = payment;
this.state = PayState.PAY_INIT;
}
public void startPay() {
changeState(PayState.PAY_INIT);
}
public void changeState(PayState state) {
onStateChanged(this.state, state);
}
public void reportError(PayError error, String detail) {
LogUtil.printPayLog("the error id is:" + error + " " + detail);
changeState(PayState.ERROR_OCCURRED);
}
private void onStateChanged(PayState oldState, PayState newState) {
LogUtil.printPayLog("oid state:" + oldState + " new state:" + newState);
this.state = newState;
handlePayStateChange();
}
private void handlePayStateChange() {
if (this.order == null || this.payment == null) {
LogUtil.printPayLog("Have not initiated payment");
return;
}
switch (this.state) {
case PAY_INIT:
order.getPayContext();
break;
case PAY_GOT_CONTEXT:
order.createOrder();
break;
case PAY_UPDATED_ORDER:
case PAY_APPLIED_ID:
case PAY_ORDER_CREATED:
payment.pay(order);
break;
case PAY_SUCCEED:
case ERROR_OCCURRED:
finishProcess();
break;
default:
LogUtil.printPayLog("state is not correct!");
finishProcess();
}
}
private void finishProcess() {
this.order = null;
this.payment = null;
this.state = PayState.PAY_INIT;
}
}
最后,訂單類層次可以參考模板模式來(lái)設(shè)計(jì),例如抽象基類負(fù)責(zé)定義訂單的操作框架和流程,具體訂單數(shù)據(jù)的生成延遲到子類中實(shí)現(xiàn)。
支付流程
本文針對(duì)Android版進(jìn)行講解主要的支付流程,IOS版流程類似。
1、客戶端實(shí)現(xiàn)
本文結(jié)合操作流程和數(shù)據(jù)流程,講述主要的實(shí)現(xiàn)方案。
首先假設(shè)訂單數(shù)據(jù)都已經(jīng)存儲(chǔ)在OrderPayModel中。
第一步:App客戶端訪問(wèn)應(yīng)用服務(wù)器,后者生成訂單編號(hào)并返回客戶端。
private void getOrderIdRequest() {
JSONObject ob = new JSONObject();
ob.put("amount", orderPayModel.getOrderPriceTotal());
ob.put("productDescription", orderPayModel.getOrderName());
ob.put("userId", orderPayModel.getUserId());
ob.put("barCoupon", orderPayModel.getOrderId());
ob.put("barId", orderPayModel.getBarId());
ob.put("count", orderPayModel.getOrderNums());
LogUtil.printPayLog("get order id request data:"
+ orderPayModel.toString());
HttpRequestFactory.getInstance().doPostRequest(Urls.ALI_PAY_APPLY, ob,
new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String content) {
super.onSuccess(content);
LogUtil.printPayLog("get order id request is handled");
PayNewOrderModel rm = new PayNewOrderModel();
rm = JSON.parseObject(content, PayNewOrderModel.class);
if (rm.getCode() != null
&& "200".equalsIgnoreCase(rm.getCode())) {
tradeNo = rm.getResult().getTrade_no();
LogUtil.printPayLog("succeed to get order id:"
+ tradeNo);
orderStr = generateOrder();
PayStateMachine.getInstance().changeState(
PayState.PAY_APPLIED_ID);
} else {
PayStateMachine.getInstance().reportError(
PayError.PAY_APPLY_ID_FAIL,
"code is not right");
}
}
@Override
public void onFailure(Throwable error, String content) {
PayStateMachine.getInstance().reportError(
PayError.PAY_APPLY_ID_FAIL,
"failed to get order id");
};
@Override
public void onFinish() {
LogUtil.LogDebug("Payment", "on get order id finish",
null);
};
});
}
第二步:組裝訂單數(shù)據(jù),包括以下幾個(gè)子步驟:
創(chuàng)建訂單數(shù)據(jù)。
private String getOrderInfo(String partner, String seller) {
String orderInfo;
// 合作者身份ID
orderInfo = "partner=" + "\"" + partner + "\"";
// 賣家支付寶賬號(hào)
orderInfo += "&seller_id=" + "\"" + seller + "\"";
// 商戶網(wǎng)站唯一訂單號(hào)
orderInfo += "&out_trade_no=" + "\"" + tradeNo + "\"";
// 商品名稱
orderInfo += "&subject=" + "\"" + orderName + "\"";
// 商品詳情
orderInfo += "&body=" + "\"" + orderDetail + "\"";
// 商品金額
orderInfo += "&total_fee=" + "\"" + totalPrice + "\"";
// orderInfo += "&total_fee=" + "\"" + "0.01" + "\"";
// 服務(wù)器異步通知頁(yè)面路徑
orderInfo += "¬ify_url=" + "\"" + Urls.ALI_PAY_NOTIFY + "\"";
// 接口名稱, 固定值
orderInfo += "&service=\"mobile.securitypay.pay\"";
// 支付類型, 固定值
orderInfo += "&payment_type=\"1\"";
// 參數(shù)編碼, 固定值
orderInfo += "&_input_charset=\"utf-8\"";
// 設(shè)置未付款交易的超時(shí)時(shí)間
// 默認(rèn)30分鐘,一旦超時(shí),該筆交易就會(huì)自動(dòng)被關(guān)閉。
// 取值范圍:1m~15d。
// m-分鐘,h-小時(shí),d-天,1c-當(dāng)天(無(wú)論交易何時(shí)創(chuàng)建,都在0點(diǎn)關(guān)閉)。
// 該參數(shù)數(shù)值不接受小數(shù)點(diǎn),如1.5h,可轉(zhuǎn)換為90m。
orderInfo += "&it_b_pay=\"30m\"";
// 支付寶處理完請(qǐng)求后,當(dāng)前頁(yè)面跳轉(zhuǎn)到商戶指定頁(yè)面的路徑.
// orderInfo += "&return_url=\"m.alipay.com\"";
// Bill: this item must not be empty! though the api demo said it
// can be.
orderInfo += "&return_url=\"m.alipay.com\"";
// 調(diào)用銀行卡支付,需配置此參數(shù),參與簽名, 固定值
// orderInfo += "&paymethod=\"expressGateway\"";
}
return orderInfo;
}
- 對(duì)訂單做RSA簽名: demo代碼中提供SingUtils類實(shí)現(xiàn)該功能,即SignUtils.sign(content, RSA_PRIVATE);
- 對(duì)簽名做 URL編碼: 調(diào)用java類庫(kù)接口,即URLEncoder.encode來(lái)實(shí)現(xiàn)。
- 將訂單數(shù)據(jù)和簽名信息組合,生成符合支付寶參數(shù)規(guī)范的數(shù)據(jù):
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
第三步:在子線程里調(diào)用PayTask的pay接口,將請(qǐng)求數(shù)據(jù)發(fā)送出去
PayTask alipay = new PayTask(PayDemoActivity.this); // 調(diào)用支付接口,獲取支付結(jié)果 String result = alipay.pay(payInfo);
第四步:收到支付處理結(jié)果的消息。支付結(jié)果的狀態(tài)碼的意義如下:
- 值為“9000”,代表支付成功;
- 值為“8000”,代表等待支付結(jié)果確認(rèn),這可能由于系統(tǒng)原因或者渠道支付原因。支付的最終結(jié)果需要由服務(wù)器端的異步通知為準(zhǔn)(支付寶將向)。
- 值為其他,代表失敗??蛻舳诵枰崾居脩簟?/strong>
注意事項(xiàng):
1、本文特別需要指出的是,也就是最容易出問(wèn)題的就是訂單數(shù)據(jù)的生成。在demo代碼的 PayDemoActivity類中,定義了getOrderInfo方法。 其中“orderInfo += "&return_url=\"m.alipay.com\"”;”在該demo代碼的注釋中,雖然說(shuō)是可以為空,但實(shí)際情況,如果為空,將導(dǎo)致支付失敗。而且憑借失敗狀態(tài)碼,難以識(shí)別具體原因。
2、支付結(jié)果,除了支付寶服務(wù)器發(fā)通知到客戶端外,也會(huì)異步通知應(yīng)用服務(wù)器??紤]到安全性,客戶端可以根據(jù)支付寶服務(wù)器的通知,進(jìn)行商業(yè)邏輯的處理,比如訂單更新等,但是支付的數(shù)據(jù)入庫(kù),需要由應(yīng)用服務(wù)器端根據(jù)異步通知進(jìn)行操作。
2、服務(wù)端實(shí)現(xiàn)
服務(wù)端基本操作包括:獲取支付寶賬號(hào)信息(為了安全,該信息放置在服務(wù)器,而不是客戶端),創(chuàng)建訂單,支付結(jié)果異步回調(diào),申請(qǐng)退款等基本操作外。另外也可能包括:更新訂單(對(duì)于支持訂單可修改的應(yīng)用),驗(yàn)證消費(fèi)碼,查詢訂單記錄,刪除訂單等操作。
本文介紹基于Java平臺(tái)的服務(wù)器方案。目前比較流行的框架組合是SpingMVC+Mybatis+Mysql。
訂單的創(chuàng)建。當(dāng)用戶下訂單時(shí),如果是新訂單(請(qǐng)求的數(shù)據(jù)沒(méi)有包括訂單編號(hào)信息),需要?jiǎng)?chuàng)建,并返回訂單號(hào)給客戶端。訂單類示例:
public class PayOrder {
public String tradeNo; //隨機(jī)編號(hào)
public String amount; //付款金額
public String status; //操作狀態(tài)
public String statusCode; //操作狀態(tài)代碼,0-未支付,10-已支付,4000-退款中,5000-已退款,6000-付款失敗,6001-取消付款,7000-已消費(fèi)
public String orderNo; //支付寶流水號(hào)
public String productDescription; //商品名稱
public String payNo; //消費(fèi)碼
public String isRefund; //是否申請(qǐng)退款
public String createTime; //創(chuàng)建時(shí)間
public String modifyTime; //修改時(shí)間
public String userId; //用戶id
public Integer id; //主鍵
public String pId; //商品id
public int buyNumber; //存儲(chǔ)購(gòu)買數(shù)量
public int vendorId; //商家ID
}
tradeNo代碼訂單編號(hào)。payNo代碼消費(fèi)編號(hào)(消費(fèi)碼)。orderNo是支付寶服務(wù)器端生成的訂單號(hào)。
@RequestMapping(value = "/payorder")
@ResponseBody
public Map<String, Object> pay(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> map = JsonPUtil.pToMap(request);
Map<String, Object> msgMap = new HashMap<String, Object>();
if (!map.isEmpty()) {
try {
log.info("執(zhí)行購(gòu)買前確認(rèn)操作:" + map);
String now = String.valueOf(System.currentTimeMillis());
String trade_no = map.get("barId").toString() + "-" + map.get("barCoupon").toString() + "-" + map.get("count").toString() + "-" + now.substring(now.length() - 6);
PayOrder alipay = new PayOrder();
alipay.setAmount(map.get("amount").toString());
alipay.setTradeNo(trade_no);
alipay.setProductDescription(map.get("productDescription").toString());
alipay.setCreateTime(now);
alipay.setStatus("待支付");
alipay.setStatusCode("0");
alipay.setExtInt1(Integer.parseInt(map.get("count").toString()));
alipay.setUserId(map.get("userId").toString());
alipay.setpId(map.get("barCoupon").toString());
alipay.setExtInt2(Integer.parseInt(map.get("barId").toString()));
int flag = alipayServiceImpl.pay(alipay);
log.info("確認(rèn)操作執(zhí)行結(jié)果:" + flag);
Map<String, Object> m = new HashMap<String, Object>();
m.put("trade_no", trade_no);
m.put("seller", new String(Base64.encode(AlipayConfig.SELLER.getBytes())));
m.put("partner", new String(Base64.encode(AlipayConfig.partner.getBytes())));
m.put("privateKey", new String(Base64.encode(AlipayConfig.ios_private_key.getBytes())));
if (flag > 0)
msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SUCCESS_CODE, "success", m);
else
msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SERVER_WRONG_CODE, "fail");
} catch (Exception e) {
e.printStackTrace();
msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SERVER_WRONG_CODE, "fail");
log.error("購(gòu)買前確認(rèn)失敗", e);
}
} else {
log.info("請(qǐng)求參數(shù)不完整");
msgMap = ResponseMessageUtil.respMsg(Constance.ILLEGAL_OPERATE, "fail");
}
return msgMap;
}
支付寶服務(wù)器回調(diào)App服務(wù)器,通知支付結(jié)果。App服務(wù)器將相應(yīng)的數(shù)據(jù)入庫(kù)后,通知支付寶服務(wù)器"success" or "fail"。
@RequestMapping(value = "/payOver")
@ResponseBody
public String payOver(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = JsonPUtil.buildMap(request);
String result_str = "fail";
if (!map.isEmpty()) {
if (AlipayUtils.checkAlipay(map, false) > 0) {// 通過(guò)支付寶驗(yàn)證
try {
log.info("執(zhí)行付款的回調(diào)函數(shù)傳遞參數(shù):" + map);
String now = String.valueOf(System.currentTimeMillis());
String status = map.get("trade_status").toString();
PayOrder alipay = new PayOrder();
alipay.setTradeNo(String.valueOf(map.get("out_trade_no")));
log.info("支付狀態(tài):" + status);
if (Constance.ALIPAY_SUCCESS_CODE.equals(status) || Constance.ALIPAY_FINISHED_CODE.equals(status)) {// 支付成功
List<Alipay> ali = alipayServiceImpl.search(alipay);
if (ali.size() == 1 && (ali.get(0).getPayNo() == null || ali.get(0).getPayNo().equals(""))) {// 消息未處理
Alipay pay = new Alipay();
pay.setTradeNo(String.valueOf(map.get("out_trade_no")));
pay.setStatus("已支付");
pay.setStatusCode("10");
pay.setIsRefund("0");
pay.setModifyTime(String.valueOf(System.currentTimeMillis()));
pay.setPayNo(new String(Base64.encode(now.substring(now.length() - 10).getBytes())));
pay.setOrderNo(String.valueOf(map.get("trade_no")));
int flag = alipayServiceImpl.payOver(pay);
log.info("用戶付款成功" + map);
if (flag > 0)
result_str = "success";
}
} else {
return result_str;
}
} catch (Exception e) {
e.printStackTrace();
log.error("回調(diào)函數(shù)獲取參數(shù)失敗", e);
return result_str;
}
}
}
return result_str;
}
以上就是基于Android支付寶支付設(shè)計(jì)和開(kāi)發(fā)方案,希望對(duì)大家學(xué)習(xí)Android軟件編程有所幫助。
相關(guān)文章
Android?Flutter實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲
當(dāng)我們回憶起小時(shí)候的經(jīng)典電腦游戲,掃雷一定是其中之一。本文將通過(guò)Flutter實(shí)現(xiàn)一個(gè)能在多端運(yùn)行的掃雷游戲,感興趣的可以了解一下2023-03-03
Flutter利用Canvas模擬實(shí)現(xiàn)微信紅包領(lǐng)取效果
這篇文章主要為大家詳細(xì)介紹了如何利用Flutter中的Canvas模擬實(shí)現(xiàn)微信紅包領(lǐng)取的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-03-03
Android關(guān)于獲取時(shí)間的記錄(小結(jié))
這篇文章主要介紹了Android關(guān)于獲取時(shí)間的記錄(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
詳解Android TabHost的多種實(shí)現(xiàn)方法 附源碼下載
這篇文章主要為大家詳細(xì)介紹了Android TabHost的多種實(shí)現(xiàn)方法 文章中針對(duì)每一種實(shí)現(xiàn)方法都附有源碼進(jìn)行下載,感興趣的小伙伴們可以參考一下2016-05-05
AndroidX下使用Activity和Fragment的變化詳解
這篇文章主要介紹了AndroidX下使用Activity和Fragment的變化詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

