Android之支付宝设计与开发

背景 

     在移动支付领域,支付宝支付占用巨大份额,根据艾瑞咨询公布的报告数据: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也可能会有不同的形式,如格式可能不同,有些支持可退款,有的不允许退款等,在这种多维度可变的情况下,支付模块的架构可以基于桥接模式。
其次,可以把支付宝支付的各个操作步骤,比如获取订单号,生成订单数据,进行支付,获取支付结果,处理异常等操作,根据状态进行划分。这样采用状态模式,提供设计的灵活性和扩展性。另外也可以设计状态机进行统一的状态切换管理。下面为参考代码:

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客户端访问应用服务器,后者生成订单编号并返回客户端。

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() {@Overridepublic 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");}}@Overridepublic void onFailure(Throwable error, String content) {PayStateMachine.getInstance().reportError(PayError.PAY_APPLY_ID_FAIL,"failed to get order id");};@Overridepublic void onFinish() {LogUtil.LogDebug("Payment", "on get order id finish",null);};});}

第二步:组装订单数据,包括以下几个子步骤:

创建订单数据。

private String getOrderInfo(String partner, String seller) {String orderInfo;// 合作者身份IDorderInfo = "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来实现。
将订单数据和签名信息组合,生成符合支付宝参数规范的数据:  

final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();

第三步:在子线程里调用PayTask的pay接口,将请求数据发送出去

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。
订单的创建。当用户下订单时,如果是新订单(请求的数据没有包括订单编号信息),需要创建,并返回订单号给客户端。订单类示例:
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;      //用户idpublic Integer id;       //主键public String pId;       //商品idpublic 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);elsemsgMap = 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"。 

@RequestMapping(value = "/payOver")@ResponseBodypublic 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;}







本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

秘境探索之一个.NET 对象从内存分配到内存回收

前方高能预警&#xff0c;新手慎入&#xff01;不听劝阻者&#xff0c;轻则郁闷堆积&#xff0c;重则生死看淡&#xff0c;对编程失去了念想&#xff0c;对生活失去了幻想&#xff01;好了&#xff0c;心理强大到NB的可以忽略前方若干警示。为了探索.NET对象的内存分配和回收销…

这五部关于宇宙的神级纪录片,带你探索未知的外太空世界

宇宙之大无奇不有&#xff0c;在你的认知里你又知道多少关于宇宙的事情&#xff0c;如果单单用外星人概括你所对宇宙的认知就真的太片面了&#xff0c;小编今天就带来下面这四部关于宇宙的硬核纪录片&#xff0c;带你真正的去了解关于宇宙的知识&#xff0c;让你遨游在宇宙的知…

Exceptionless服务端+kibana部署实时日志纪要

安装软件列表Exceptionless.4.1.2861.zipelasticsearch-5.6.14.zipkibana-5.6.14-windows-x86.zip安装准备1. 在D盘下创建Exceptionless文件夹2. 拷贝需要安装的软件(参照上面安装软件列表)到Exceptionless文件夹下3. 安装JDK 1.8 (C环境中已经有安装, 步骤忽略)4. …

好心帮男朋友洗衣服,他却要分手??

1 每天一个分手小技巧&#xff08;via.平民窟公主&#xff09;▼2 导航最近的加油站&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 单位停电发的蜡烛&#xff08;via.蜻蜓队长&#xff09;▼4 我也不知道原来有人这样穿啊&#xff01;&#xff08;素材来源网络&…

Android之如何解决右上角不显示3个点的菜单

之前写过小例子,发现菜单栏右上角的那3个点老是显示不出来,今天终于解决了,不废话,先爆照。 我之前的代码menu_main.xml 文件如下 <menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools&quo…

大型网站架构系列:电商网站架构案例

为什么80%的码农都做不了架构师&#xff1f;>>> #0 系列目录# 大型分布式网站架构大型分布式网站架构技术总结大型网站架构系列&#xff1a;电商网站架构案例#1 电商案例原因# 分布式大型网站&#xff0c;目前看主要有几类1.大型门户&#xff0c;比如网易&#xff…

八、结构模式之组合(Composite)模式

组合模式属于对象的结构模式&#xff0c;有时又叫做部分-整体模式&#xff0c;组合模式将对象组织到树结构中&#xff0c;可以用来描述整体与部分的联系。其可以使客户端将单纯元素和组合元素同等对待。 当需求中是体现部分与整体层次的结构时&#xff0c;以及你希望用户可以忽…

代言男科、站台微商、变身神棍....这些科学家被捧了几十年,黑历史曝光后,让人三观尽毁......

全世界只有3.14 % 的人关注了爆炸吧知识说到诺贝尔奖得主&#xff0c;尤其科学类奖项&#xff0c;吃瓜群众的感受常常是:不明觉厉。关于他们有多牛x的故事&#xff0c;你肯定听了不少。但你一定很少听过&#xff0c;诺奖得主转而研究伪科学&#xff0c;或者为了恰饭疯狂掉节操的…

拉屎能赚钱?在马桶上月入过万?原来卫生间里还有这么多隐藏福利,超模君都惊了……

全世界只有3.14 % 的人关注了爆炸吧知识模友们&#xff0c;你们有过“带薪拉屎”的经历吗&#xff1f;没错&#xff0c;假如我们每天花10分钟“带薪拉屎”&#xff0c;那一年大概能积攒下来40小时&#xff0c;假如我们每天工作8小时&#xff0c;等于多了5天年假&#xff0c;白嫖…

Android之让图片匀速旋转效果

图片匀速旋转 当我们更新的时候,需要把更新小图标旋转起来,不废话,先爆照 介绍动画: Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果;第二类是 Frame 动画,即顺序播放事先…

dotnet 使用 Infer# 自动分析代码缺陷

本文告诉大家如何使用 Infer# 开源库配合 GitHub 的 Action 实现自动分析代码缺陷&#xff0c;如找到可空引用或线程安全等问题这是一个在 GitHub 上完全开源的仓库&#xff0c;请看 https://github.com/microsoft/infersharp刚好今天收到了 Infer# 发布 1.2 版本博客&#xff…

发了篇paper,双非二本的她直博浙大

全世界只有3.14 % 的人关注了爆炸吧知识大家好&#xff0c;我是小南&#xff0c;本科就读于国内某双非二本院校&#xff0c;于2019年拿到了浙江大学的直博录取函。很多人问我从二本院校成功跨越到国内知名高校的秘诀是什么&#xff0c;我个人觉得&#xff0c;除开运气&#xff…

PS网页设计教程XVI——在PS中创建一个摩登实验室风格的网页设计

作为编码者&#xff0c;美工基础是偏弱的。我们可以参考一些成熟的网页PS教程&#xff0c;提高自身的设计能力。套用一句话&#xff0c;“熟读唐诗三百首&#xff0c;不会作诗也会吟”。 本系列的教程来源于网上的PS教程&#xff0c;都是国外的&#xff0c;全英文的。本人尝试翻…

好评率超高的9个公众号,值得收藏

不管世界变化多快&#xff0c;阅读仍是个不过时的动作。今天就为大家推荐几个优质公众号&#xff0c;从社会热点、读书成长、生活新知&#xff0c;你的碎片化时间&#xff0c;我们包了&#xff01;一起来看看呀~长按二维码&#xff0c;“识别图中二维码”即可订阅————电商头…

.NET 6新特性试用 | 文件范围的命名空间

前言在前面的《隐式using指令》文章中&#xff0c;我们介绍了global using&#xff0c;可以减少我们代码文件中的“噪音”&#xff0c;使代码更简洁。今天&#xff0c;我们介绍.NET 6中另一个简化代码的方式——文件范围的命名空间。Demo当我们向项目中添加一个类文件时&#x…

Android之三大图片缓存原理、特性对比

一. 四大图片缓存基本信息 Universal ImageLoader 是很早开源的图片缓存&#xff0c;在早期被很多应用使用。Picasso 是 Square 开源的项目&#xff0c;且他的主导者是 JakeWharton&#xff0c;所以广为人知。Glide 是 Google 员工的开源项目&#xff0c;被一些 Google App 使用…

他读博期间连发3篇Science,28岁任武大教授后再发Nature!

全世界只有3.14 % 的人关注了爆炸吧知识来源&#xff1a;募格课堂整合自武汉大学、武汉晚报、半月谈等有这样一位学者&#xff0c;读博期间以一作身份发表3篇Science,28岁成为武汉大学化学与分子科学学院教授再发2篇Nature&#xff0c;35岁荣获杰青......今年9月&#xff0c;他…

Android 页面布局xd,Adobe XD强大的布局系列工具 助你事半功倍

数字设计有多种形式-从移动端、桌面网站到显示屏应用程序、智能助手、车载娱乐系统(例如CarPlay和Android Auto)等等。为当今的应用程序打造数字体验意味着需要针对各种屏幕尺寸、布局和交互类型进行设计。作为设计师&#xff0c;您的时间应该花在用户体验上&#xff0c;而不是…

如果觉得午休时间太短怎么办?

1 假装自己戴了口罩&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 正义不会迟到博主是男的&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 阅读理解有多难&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 下雨天就不要爬树了&…

WPF 实现温度计

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织在WPF中没有现成的温度计控件&#xff0c;所以我们自己实现一个。微信群人数太多入群请添加小编微信号&#xff08;yanjinhuawechat&#xff09;或&#xff08;W_Feng_aiQ&#xff09;邀请入群&am…