背景  
     在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固。财付通支付的发力点在微信支付和手Q支付,在移动支付格局中取得了10.0%的市场份额,排名第二。 
     支付宝在移动支付领域的统治地位,使得我们有必要梳理支付宝移动开发流程。本文写作的目的就是梳理支付流程,从架构层面讲述如何在移动应用中嵌入支付宝支付功能,以及指出哪些地方存在开发陷阱。

准备  
     首先,支付宝SDK下载主页的地址是: https://b.alipay.com/order/productDetail.htm?productId=2013080604609654&tabId=4#ps-tabinfo-hash 。这个地址隐藏很深,所以这里有必要指出。 
     按照说明,首先需要申请支付宝支付账号。这方面根据网站说明进行申请即可。一般需要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版流程类似。

从操作角度看支付流程:

操作2(调用支付接口)和操作7(接口返回支付结果):App与支付宝API的交互。

操作5(异步发送支付通知):支付宝服务器与App后台的交互。

从数据流角度看支付流程:

客户端实现

本文结合操作流程和数据流程,讲述主要的实现方案。

首先假设订单数据都已经存储在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”,代表等待支付结果确认,这可能由于系统原因或者渠道支付原因。支付的最终结果需要由服务器端的异步通知为准(支付宝将向)。
  • 值为其他,代表失败。客户端需要提示用户。

注意事项:

  • 本文特别需要指出的是,也就是最容易出问题的就是订单数据的生成。在demo代码的 PayDemoActivity类中,定义了getOrderInfo方法。 其中“orderInfo += "&return_url=\"m.alipay.com\"”;”在该demo代码的注释中,虽然说是可以为空,但实际情况,如果为空,将导致支付失败。而且凭借失败状态码,难以识别具体原因。
  • 支付结果,除了支付宝服务器发通知到客户端外,也会异步通知应用服务器。考虑到安全性,客户端可以根据支付宝服务器的通知,进行商业逻辑的处理,比如订单更新等,但是支付的数据入库,需要由应用服务器端根据异步通知进行操作。

服务端实现

服务端基本操作包括:获取支付宝账号信息(为了安全,该信息放置在服务器,而不是客户端),创建订单,支付结果异步回调,申请退款等基本操作外。另外也可能包括: 更新订单(对于支持订单可修改的应用),验证消费码,查询订单记录,删除订单等操作。

本文介绍基于Java平台的服务器方案。目前比较流行的框架组合是SpingMVC+Mybatis+Mysql。

订单的创建。当用户下订单时,如果是新订单(请求的数据没有包括订单编号信息),需要创建,并返回订单号给客户端。 订单类示例:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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是支付宝服务器端生成的订单号。

复制代码

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
@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;
  }

支付宝支付设计和开发方案相关推荐

  1. android支付界面设计,Android支付宝支付设计开发

    在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固.财付通支付的发力点在微信支付和手Q支付,在移动支付格 ...

  2. 支付宝支付接入-服务器端开发

    入职新公司后,被安排做支付宝支付服务器端的开发,在阅读了支付宝的开发文档(https://docs.open.alipay.com/54/106370/)后,便开始了开发. 背景:我们公司接入支付宝支 ...

  3. [科技论文写作]基于Android的支付宝APP设计与开发实现

    文章目录 前言 正文 基于Android的支付宝APP设计与实现 1 选题背景 2 选题价值 2.1 理论价值 2.2 实践价值 3 文献综述 4 拟研究内容 5 拟解决关键问题 6 拟采用的研究方法 ...

  4. android移动支付——支付宝支付,android开发游戏加速器

    上面的支付流程细化下来就是: 1.浏览商品 2.把要买的商品加入购物车 3.把商品拿到收银台,收银人员处理商品信息 4.告诉收银员支付方式 5.选择支付方式进行支付 6.处理支付结果(成功.失败.取消 ...

  5. 支付宝支付--即时到账 开发总结

    在支付宝支付的开发过程中,通过自己的理解和尝试做出一点流程总结记录: 一.前往 https://doc.open.alipay.com/doc2/alipayDocIndex.htm 支付宝开放平台 ...

  6. ThinkPHP5.0+APP+支付宝支付 服务端开发

    工作环境,支付宝账号是公司的,app应用和秘钥配置都是我自己申请的,这其中的流程暂且跳过. 一.准备工作 appid.应用私钥.应用公钥.支付宝公钥 二.配置文件 'alipay'=>['app ...

  7. 记一次支付宝支付的功能开发

    背景: 公司需要增加一项支付宝PC端的收款功能 解决: 使用的支付宝官方文档中的电脑网站支付->统一收单下单并支付,当然,我们的支付宝账号需要开通该产品.官方API连接:https://open ...

  8. 四、支付宝支付对接 - SDK开发、业务对接、支付回调、支付组件(2)

  9. Android之支付宝设计与开发

    背景 在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固.财付通支付的发力点在微信支付和手Q支付,在移动 ...

最新文章

  1. nyoj19 全排列
  2. linux安装硬盘命令,硬盘安装linux的两条命令
  3. COMMCONFIG进行配置的WIN32 API
  4. SQL Server 审核(Audit)-- 创建数据库级别的审核
  5. accdb原有的数据怎么清除_VBA中利用数组对数据批量处理的方法
  6. Java Enterprise软件与应有的内容
  7. Vue 中是如何解析 template 字符串为 VNode 的?
  8. ubuntu 自动加载ko_Ubuntu启动后自动加载ext4分区
  9. android view 屏幕外,安卓如何让View往屏幕外隐藏?
  10. doc转docx文件会乱吗_我电脑里所有Word的doc格式都变成了docx格式.传Word文件给别人都打不开.请问为什么?...
  11. 教你安装ps,pr,ae,ai等Adobe软件,办公必备
  12. OpenGLCG技术之Render To Texture
  13. android 6.0 usb网卡,Android安卓6.0使用技巧:让手机化身网卡和声卡
  14. Linux查看目录大小——du命令
  15. mysql按照音序排列_如何按音序排列
  16. 更换光猫后网速变慢的解决办法
  17. 三层交换机实现vlan间通信
  18. 2021年电工(初级)实操考试视频及电工(初级)理论考试
  19. 在IDC机房,1m宽带下载速度是多少?
  20. 阿丹学理财之房产投资

热门文章

  1. 一元三次方程求解matlab_七年级下学期一元一次方程章节2020年高频典型题集(举一反三)...
  2. 淘宝直播间推流码获取
  3. php 字符串 分割,PHP 切割字符串
  4. vue页面切换动画-类似于轮播效果、渲染列表给每一行数据加上显示动画
  5. SQL语句各种join用法(图文)
  6. 用VB.NET实现AUTOCAD中鼠标选中后显示事件
  7. SV中关键字用法学习笔记
  8. 自组网中继台_自组网电台,传输稳定距离远明溪
  9. oracle认证考试_Oracle云认证–通过此3小时免费课程通过考试
  10. excel怎么设置条件格式