Android支付宝支付设计开发
在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据:2014Q3,支付宝斩获了82.6%的市场份额,在移动支付的霸主地位越来越稳固。财付通支付的发力点在微信支付和手Q支付,在移动支付格局中取得了10.0%的市场份额,排名第二。
支付宝在移动支付领域的统治地位,使得我们有必要梳理支付宝移动开发流程。本文写作的目的就是梳理支付流程,从架构层面讲述如何在移动应用中嵌入支付宝支付功能,以及指出哪些地方存在开发陷阱。
准备
按照说明,首先需要申请支付宝支付账号。这方面根据网站说明进行申请即可。一般需要2周左右的时间批准下来。
申请成功后账号信息包括合作者身份IDpartner,卖家支付宝账号seller_id,以及私钥privateKey等。这三项将用于开发过程。
在官网上下载移动支付集成开发包。解压后,发现其下包括三个文件夹(在英文Mac系统下文件名显示为乱码):
- “商户接入支付宝收银台界面展示标准”:讲的是如何使用支付宝Logo。
- “支付宝钱包支付接口开发包2.0标准版”:用于支付,包括客户端和服务器端开发。
- “即时到账批量退款有密接口refund_fastpay_by_platform_pwd”:用于到账及批量退款,只需要服务器端操作处理。
后两个文件夹,都包括4方面内容:接口文档,接入与使用规则,demo代码,以及版本更新说明。
架构设计
首先,对于一个实际的App应用而言,可能会包括多种支付方式,因此可以采用设计模式中的策略Strategy模式来设计支付功能模块,支付宝支付作为其中的一个策略,pay方法是支付算法。
如果除了支付方式paymentmethod变化,订单order也可能会有不同的形式,如格式可能不同,有些支持可退款,有的不允许退款等,在这种多维度可变的情况下,支付模块的架构可以基于桥接模式。
其次,可以把支付宝支付的各个操作步骤,比如获取订单号,生成订单数据,进行支付,获取支付结果,处理异常等操作,根据状态进行划分。这样采用状态模式,提供设计的灵活性和扩展性。另外也可以设计状态机进行统一的状态切换管理。下面为参考代码:
publicclassPayStateMachine{ /*allpossiblestateofpayment*/ publicenumPayState{PAY_INIT,PAY_GOT_CONTEXT,PAY_UPDATED_ORDER,PAY_APPLIED_ ID,PAY_ORDER_CREATED,PAY_SUCCEED,ERROR_OCCURRED} /*errorsmayoccurredduringpayment*/ publicenumPayError{ PAY_GET_CONTEXT_FAIL,PAY_UPDATE_ORDER_FAIL,PAY_APPLY_ID_FAIL,PAY_FAIL } privatestaticPayStateMachineinstance; privatePayStatestate; privateIOrderorder; privateIPaymentpayment; privatePayStateMachine(){ } publicstaticPayStateMachinegetInstance(){ if(instance==null){ instance=newPayStateMachine(); } returninstance; } publicvoidinitPayment(IOrderorder,IPaymentpayment){ this.order=order; this.payment=payment; this.state=PayState.PAY_INIT; } publicvoidstartPay(){ changeState(PayState.PAY_INIT); } publicvoidchangeState(PayStatestate){ onStateChanged(this.state,state); } publicvoidreportError(PayErrorerror,Stringdetail){ LogUtil.printPayLog("theerroridis:"+error+""+detail); changeState(PayState.ERROR_OCCURRED); } privatevoidonStateChanged(PayStateoldState,PayStatenewState){ LogUtil.printPayLog("oidstate:"+oldState+"newstate:"+newState); this.state=newState; handlePayStateChange(); } privatevoidhandlePayStateChange(){ if(this.order==null||this.payment==null){ LogUtil.printPayLog("Havenotinitiatedpayment"); return; } switch(this.state){ casePAY_INIT: order.getPayContext(); break; casePAY_GOT_CONTEXT: order.createOrder(); break; casePAY_UPDATED_ORDER: casePAY_APPLIED_ID: casePAY_ORDER_CREATED: payment.pay(order); break; casePAY_SUCCEED: caseERROR_OCCURRED: finishProcess(); break; default: LogUtil.printPayLog("stateisnotcorrect!"); finishProcess(); } } privatevoidfinishProcess(){ this.order=null; this.payment=null; this.state=PayState.PAY_INIT; } }
最后,订单类层次可以参考模板模式来设计,例如抽象基类负责定义订单的操作框架和流程,具体订单数据的生成延迟到子类中实现。
支付流程
本文针对Android版进行讲解主要的支付流程,IOS版流程类似。
1、客户端实现
本文结合操作流程和数据流程,讲述主要的实现方案。
首先假设订单数据都已经存储在OrderPayModel中。
第一步:App客户端访问应用服务器,后者生成订单编号并返回客户端。
privatevoidgetOrderIdRequest(){ JSONObjectob=newJSONObject(); 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("getorderidrequestdata:" +orderPayModel.toString()); HttpRequestFactory.getInstance().doPostRequest(Urls.ALI_PAY_APPLY,ob, newAsyncHttpResponseHandler(){ @Override publicvoidonSuccess(Stringcontent){ super.onSuccess(content); LogUtil.printPayLog("getorderidrequestishandled"); PayNewOrderModelrm=newPayNewOrderModel(); rm=JSON.parseObject(content,PayNewOrderModel.class); if(rm.getCode()!=null &&"200".equalsIgnoreCase(rm.getCode())){ tradeNo=rm.getResult().getTrade_no(); LogUtil.printPayLog("succeedtogetorderid:" +tradeNo); orderStr=generateOrder(); PayStateMachine.getInstance().changeState( PayState.PAY_APPLIED_ID); }else{ PayStateMachine.getInstance().reportError( PayError.PAY_APPLY_ID_FAIL, "codeisnotright"); } } @Override publicvoidonFailure(Throwableerror,Stringcontent){ PayStateMachine.getInstance().reportError( PayError.PAY_APPLY_ID_FAIL, "failedtogetorderid"); }; @Override publicvoidonFinish(){ LogUtil.LogDebug("Payment","ongetorderidfinish", null); }; }); }
第二步:组装订单数据,包括以下几个子步骤:
创建订单数据。
privateStringgetOrderInfo(Stringpartner,Stringseller){ StringorderInfo; //合作者身份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:thisitemmustnotbeempty!thoughtheapidemosaidit //canbe. orderInfo+="&return_url=\"m.alipay.com\""; //调用银行卡支付,需配置此参数,参与签名,固定值 //orderInfo+="&paymethod=\"expressGateway\""; } returnorderInfo; }
- 对订单做RSA签名: demo代码中提供SingUtils类实现该功能,即SignUtils.sign(content,RSA_PRIVATE);
- 对签名做URL编码: 调用java类库接口,即URLEncoder.encode来实现。
- 将订单数据和签名信息组合,生成符合支付宝参数规范的数据:
finalStringpayInfo=orderInfo+"&sign=\""+sign+"\"&"+getSignType();
第三步:在子线程里调用PayTask的pay接口,将请求数据发送出去
PayTaskalipay=newPayTask(PayDemoActivity.this); //调用支付接口,获取支付结果 Stringresult=alipay.pay(payInfo);
第四步:收到支付处理结果的消息。支付结果的状态码的意义如下:
- 值为“9000”,代表支付成功;
- 值为“8000”,代表等待支付结果确认,这可能由于系统原因或者渠道支付原因。支付的最终结果需要由服务器端的异步通知为准(支付宝将向)。
- 值为其他,代表失败。客户端需要提示用户。
注意事项:
1、本文特别需要指出的是,也就是最容易出问题的就是订单数据的生成。在demo代码的PayDemoActivity类中,定义了getOrderInfo方法。其中“orderInfo+="&return_url=\"m.alipay.com\"”;”在该demo代码的注释中,虽然说是可以为空,但实际情况,如果为空,将导致支付失败。而且凭借失败状态码,难以识别具体原因。
2、支付结果,除了支付宝服务器发通知到客户端外,也会异步通知应用服务器。考虑到安全性,客户端可以根据支付宝服务器的通知,进行商业逻辑的处理,比如订单更新等,但是支付的数据入库,需要由应用服务器端根据异步通知进行操作。
2、服务端实现
服务端基本操作包括:获取支付宝账号信息(为了安全,该信息放置在服务器,而不是客户端),创建订单,支付结果异步回调,申请退款等基本操作外。另外也可能包括:更新订单(对于支持订单可修改的应用),验证消费码,查询订单记录,删除订单等操作。
本文介绍基于Java平台的服务器方案。目前比较流行的框架组合是SpingMVC+Mybatis+Mysql。
订单的创建。当用户下订单时,如果是新订单(请求的数据没有包括订单编号信息),需要创建,并返回订单号给客户端。订单类示例:
publicclassPayOrder{ publicStringtradeNo;//随机编号 publicStringamount;//付款金额 publicStringstatus;//操作状态 publicStringstatusCode;//操作状态代码,0-未支付,10-已支付,4000-退款中,5000-已退款,6000-付款失败,6001-取消付款,7000-已消费 publicStringorderNo;//支付宝流水号 publicStringproductDescription;//商品名称 publicStringpayNo;//消费码 publicStringisRefund;//是否申请退款 publicStringcreateTime;//创建时间 publicStringmodifyTime;//修改时间 publicStringuserId;//用户id publicIntegerid;//主键 publicStringpId;//商品id publicintbuyNumber;//存储购买数量 publicintvendorId;//商家ID } tradeNo代码订单编号。payNo代码消费编号(消费码)。orderNo是支付宝服务器端生成的订单号。 @RequestMapping(value="/payorder") @ResponseBody publicMap<String,Object>pay(HttpServletRequestrequest,HttpServletResponseresponse){ Map<String,Object>map=JsonPUtil.pToMap(request); Map<String,Object>msgMap=newHashMap<String,Object>(); if(!map.isEmpty()){ try{ log.info("执行购买前确认操作:"+map); Stringnow=String.valueOf(System.currentTimeMillis()); Stringtrade_no=map.get("barId").toString()+"-"+map.get("barCoupon").toString()+"-"+map.get("count").toString()+"-"+now.substring(now.length()-6); PayOrderalipay=newPayOrder(); 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())); intflag=alipayServiceImpl.pay(alipay); log.info("确认操作执行结果:"+flag); Map<String,Object>m=newHashMap<String,Object>(); m.put("trade_no",trade_no); m.put("seller",newString(Base64.encode(AlipayConfig.SELLER.getBytes()))); m.put("partner",newString(Base64.encode(AlipayConfig.partner.getBytes()))); m.put("privateKey",newString(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(Exceptione){ e.printStackTrace(); msgMap=ResponseMessageUtil.respMsg(Constance.BASE_SERVER_WRONG_CODE,"fail"); log.error("购买前确认失败",e); } }else{ log.info("请求参数不完整"); msgMap=ResponseMessageUtil.respMsg(Constance.ILLEGAL_OPERATE,"fail"); } returnmsgMap; }
支付宝服务器回调App服务器,通知支付结果。App服务器将相应的数据入库后,通知支付宝服务器"success"or"fail"。
@RequestMapping(value="/payOver") @ResponseBody publicStringpayOver(HttpServletRequestrequest,HttpServletResponseresponse){ Map<String,String>map=JsonPUtil.buildMap(request); Stringresult_str="fail"; if(!map.isEmpty()){ if(AlipayUtils.checkAlipay(map,false)>0){//通过支付宝验证 try{ log.info("执行付款的回调函数传递参数:"+map); Stringnow=String.valueOf(System.currentTimeMillis()); Stringstatus=map.get("trade_status").toString(); PayOrderalipay=newPayOrder(); 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(""))){//消息未处理 Alipaypay=newAlipay(); 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(newString(Base64.encode(now.substring(now.length()-10).getBytes()))); pay.setOrderNo(String.valueOf(map.get("trade_no"))); intflag=alipayServiceImpl.payOver(pay); log.info("用户付款成功"+map); if(flag>0) result_str="success"; } }else{ returnresult_str; } }catch(Exceptione){ e.printStackTrace(); log.error("回调函数获取参数失败",e); returnresult_str; } } } returnresult_str; }
以上就是基于Android支付宝支付设计和开发方案,希望对大家学习Android软件编程有所帮助。