在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固。财付通支付的发力点在微信支付和手Q支付,在移动支付格局中取得了10.0%的市场份额,排名第二。
支付宝在移动支付领域的统治地位,使得我们有必要梳理支付宝移动开发流程。本文写作的目的就是梳理支付流程,从架构层面讲述如何在移动应用中嵌入支付宝支付功能,以及指出哪些地方存在开发陷阱。
准备
按照说明,首先需要申请支付宝支付账号。这方面根据网站说明进行申请即可。一般需要2周左右的时间批准下来。
申请成功后账号信息包括 合作者身份ID partner, 卖家支付宝账号 seller_id,以及私钥 privateKey等。这三项将用于开发过程。
在官网上下载移动支付集成开发包。解压后, 发现其下包括三个文件夹(在英文Mac系统下文件名显示为乱码):
- “商户接入支付宝收银台界面展示标准”:讲的是如何使用支付宝Logo。
- “支付宝钱包支付接口开发包2.0标准版”:用于支付,包括客户端和服务器端开发。
- “即时到账批量退款有密接口refund_fastpay_by_platform_pwd”:用于到账及批量退款,只需要服务器端操作处理。
后两个文件夹,都包括4方面内容:接口文档,接入与使用规则,demo代码,以及版本更新说明。
架构设计
首先,对于一个实际的App应用而言,可能会包括多种支付方式,因此可以采用设计模式中的策略Strategy模式来设计支付功能模块,支付宝支付作为其中的一个策略,pay方法是支付算法。
如果除了支付方式payment method变化,订单order也可能会有不同的形式,如格式可能不同,有些支持可退款,有的不允许退款等,在这种多维度可变的情况下,支付模块的架构可以基于桥接模式。
其次,可以把支付宝支付的各个操作步骤,比如获取订单号,生成订单数据,进行支付,获取支付结果,处理异常等操作,根据状态进行划分。这样采用状态模式,提供设计的灵活性和扩展性。另外也可以设计状态机进行统一的状态切换管理。下面为参考代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
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;
}
}
|
最后,订单类层次可以参考模板模式来设计,例如抽象基类负责定义订单的操作框架和流程,具体订单数据的生成延迟到子类中实现。
支付流程
本文针对Android版进行讲解主要的支付流程,IOS版流程类似。
1、客户端实现
本文结合操作流程和数据流程,讲述主要的实现方案。
首先假设订单数据都已经存储在OrderPayModel中。
第一步:App客户端访问应用服务器,后者生成订单编号并返回客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
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 );
};
});
}
|
第二步:组装订单数据,包括以下几个子步骤:
创建订单数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
private String getOrderInfo(String partner, String seller) {
String orderInfo;
// 合作者身份ID
orderInfo = "partner=" + "\"" + partner + "\"" ;
// 卖家支付宝账号
orderInfo += "&seller_id=" + "\"" + seller + "\"" ;
// 商户网站唯一订单号
orderInfo += "&out_trade_no=" + "\"" + tradeNo + "\"" ;
// 商品名称
orderInfo += "&subject=" + "\"" + orderName + "\"" ;
// 商品详情
orderInfo += "&body=" + "\"" + orderDetail + "\"" ;
// 商品金额
orderInfo += "&total_fee=" + "\"" + totalPrice + "\"" ;
// orderInfo += "&total_fee=" + "\"" + "0.01" + "\"";
// 服务器异步通知页面路径
orderInfo += "¬ify_url=" + "\"" + Urls.ALI_PAY_NOTIFY + "\"" ;
// 接口名称, 固定值
orderInfo += "&service=\"mobile.securitypay.pay\"" ;
// 支付类型, 固定值
orderInfo += "&payment_type=\"1\"" ;
// 参数编码, 固定值
orderInfo += "&_input_charset=\"utf-8\"" ;
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。
// m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
orderInfo += "&it_b_pay=\"30m\"" ;
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径.
// 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\"" ;
// 调用银行卡支付,需配置此参数,参与签名, 固定值
// orderInfo += "&paymethod=\"expressGateway\"";
}
return orderInfo;
}
|
- 对订单做RSA签名: demo代码中提供SingUtils类实现该功能,即SignUtils.sign(content, RSA_PRIVATE);
- 对签名做 URL编码: 调用java类库接口,即URLEncoder.encode来实现。
- 将订单数据和签名信息组合,生成符合支付宝参数规范的数据:
1
|
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
|
第三步:在子线程里调用PayTask的pay接口,将请求数据发送出去
1
2
3
|
PayTask alipay = new PayTask(PayDemoActivity. this );
// 调用支付接口,获取支付结果
String result = alipay.pay(payInfo);
|
第四步:收到支付处理结果的消息。支付结果的状态码的意义如下:
- 值为“9000”,代表支付成功;
- 值为“8000”,代表等待支付结果确认,这可能由于系统原因或者渠道支付原因。支付的最终结果需要由服务器端的异步通知为准(支付宝将向)。
- 值为其他,代表失败。客户端需要提示用户。
注意事项:
1、本文特别需要指出的是,也就是最容易出问题的就是订单数据的生成。在demo代码的 PayDemoActivity类中,定义了getOrderInfo方法。 其中“orderInfo += "&return_url=\"m.alipay.com\"”;”在该demo代码的注释中,虽然说是可以为空,但实际情况,如果为空,将导致支付失败。而且凭借失败状态码,难以识别具体原因。
2、支付结果,除了支付宝服务器发通知到客户端外,也会异步通知应用服务器。考虑到安全性,客户端可以根据支付宝服务器的通知,进行商业逻辑的处理,比如订单更新等,但是支付的数据入库,需要由应用服务器端根据异步通知进行操作。
2、服务端实现
服务端基本操作包括:获取支付宝账号信息(为了安全,该信息放置在服务器,而不是客户端),创建订单,支付结果异步回调,申请退款等基本操作外。另外也可能包括:更新订单(对于支持订单可修改的应用),验证消费码,查询订单记录,删除订单等操作。
本文介绍基于Java平台的服务器方案。目前比较流行的框架组合是SpingMVC+Mybatis+Mysql。
订单的创建。当用户下订单时,如果是新订单(请求的数据没有包括订单编号信息),需要创建,并返回订单号给客户端。订单类示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public class PayOrder {
public String tradeNo; //随机编号
public String amount; //付款金额
public String status; //操作状态
public String statusCode; //操作状态代码,0-未支付,10-已支付,4000-退款中,5000-已退款,6000-付款失败,6001-取消付款,7000-已消费
public String orderNo; //支付宝流水号
public String productDescription; //商品名称
public String payNo; //消费码
public String isRefund; //是否申请退款
public String createTime; //创建时间
public String modifyTime; //修改时间
public String userId; //用户id
public Integer id; //主键
public String pId; //商品id
public int buyNumber; //存储购买数量
public int vendorId; //商家ID
}
tradeNo代码订单编号。payNo代码消费编号(消费码)。orderNo是支付宝服务器端生成的订单号。
@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( "执行购买前确认操作:" + 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( "确认操作执行结果:" + 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( "购买前确认失败" , e);
}
} else {
log.info( "请求参数不完整" );
msgMap = ResponseMessageUtil.respMsg(Constance.ILLEGAL_OPERATE, "fail" );
}
return msgMap;
}
|
支付宝服务器回调App服务器,通知支付结果。App服务器将相应的数据入库后,通知支付宝服务器"success" or "fail"。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
@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 ) { // 通过支付宝验证
try {
log.info( "执行付款的回调函数传递参数:" + 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( "支付状态:" + 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( "回调函数获取参数失败" , e);
return result_str;
}
}
}
return result_str;
}
|
以上就是基于Android支付宝支付设计和开发方案,希望对大家学习Android软件编程有所帮助。