基于IOS下的支付宝SDK的学习与使用——实现产品支付(一)
首先声明本篇博文是作者花原创,如果转载请说明出处,仅供大家学习参考。
最近自己研究的技术点主要是,基于支付宝官方文档实现支付功能和基于百度地图SDK自定义实现多气泡同时间出现的开发。但是由于好多原因,这两块都没有去学习,今天我将在这里分享一下,我对支付的学习成果。
在研读下文之前,我希望你了解一下一些信息:
请求:手机客户端以字符串形式把需要传输的数据发送给接收方的过程。
通知:服务器异步通知。支付宝根据得到的数据处理完成后,支付宝的服务器主动发起通知给商户的网站,同时携带处理完成的结果信息反馈给商户网站。
返回:支付宝以字符串形式直接把处理结果数据返回给手机客户端。
服务市场 商家通过服务市场上传自己的营业执照等信息,得到自己在支付宝服务器中唯一的一个商户ID一个16位的字符串,和一个支付宝收款账号。
PID PID就是一个合作身份,商家在支付宝注册成为使用支付宝交易方式的商家才有PID身份,在交易的过程中支付宝会对数据进行加密,合作身份PID能够使用的加密方式主要有三种签名方式,分别是:MD5、RSA、DSA。PID可以根据不同的功能:如 快捷登录、移动支付、即时到账收款、手机网站支付等等 去选择加密签名方式,但是有的接口只支持RSA或只支持MD5。参照 注意商户支付宝公钥一定要保护好,这个关乎商户的财产安全。
那么公钥与私钥有什么作用呢?生成的公、私钥的方法官方的文档已经给了说明。公钥的和私钥需要配合成对的使用,在电商类的App中缺一不可。官方文档有这句话 “支付宝提供商户接口产品时,会自主提供一个保障商户接入安全的一组信息及其对应的配置平台,这组信息就是密钥。由商户密钥与支付宝密钥交换后与支付宝商户标识(如partnerID、APPID等)绑定。” 可见秘钥的作用:确保用户能安全的接入支付宝接口(公钥);确保为用户配置用户可用的处理服务器平台(私钥);如果私钥经过解密算法与支付宝服务器保存的商户公钥信息匹配成功,就能够进行交易。
在接口调用的时候需要的准备工作,下面来自于参考
准备工作 1、确定签名方式 支付宝支持的签名方式有MD5、RSA、DSA三种,但有些接口只支持RSA或只支持MD5。如:只支持RSA签名的有无线快捷支付等。 2、确定开发语言 支付宝不限制商户使用的开发语言是什么,因为支付宝的外部接口只要能通过HTTP协议传输的了数据就可以。因此不论任何一种开发语言,且不论是B/S架构还是C/S架构,商户可以根据自身的业务需求编写代码。 3、确定编码格式 支付宝支持的编码格式只有utf-8、gbk或gb2312。如果客户是其他编码格式,建议转成utf-8请求。 4、确定服务器配置 PHP开发语言时,是否有开CURL服务、SSL服务 notify_url、return_url回调页面路径是http还是https 如:notify_url=http://商户网站/alipay/notify_url.jsp、notify_url=https://商户网站/alipay/notify_url.jsp 如果是https,那么需要安装ssl证书,证书要求有如下两点: 要求“根证书缺省内置在JDK 1.6的信任根证书库中”,如果不理解请咨询证书供应商,提出:要求SSL证书由JDK?1.6.0_21缺省内置的根CA签发。 只支持官方机构颁发的正版SSL证书,不支持自签名。 服务器有无绑定IP、端口等限制 商户端是否有对DNS设置,是否有做防火墙策略,是否有限制端口。 需要开通的端口:80或443 不建议绑定绑定支付宝IP,因为支付宝IP不是永久不变的。
确定要传递给支付宝的参数 1、这些参数怎么来的? 每个接口对应一个接口技术文档,文档中提到的请求参数,就是可以选择要传递的参数。 2、怎么确定这些参数? 1)基本参数 partner、_input_charset、sign、sign_type、service这些属于基本参数,是必须要传递的参数。 2)业务参数 ①在接口技术文档的请求参数列表中,不可空的参数是必须要传递的参数。 如:即时到账中total_fee、subject、out_trade_no等 ②根据商户自身的业务需求或根据支付宝接口对应的特殊业务规则,有些参数也是需要传递过来的 如:即时到账中如果开通了防钓鱼策略,那么anti_phishing_key、exter_invoke_ip必须传递。 3、怎么集合这些参数? 拿即时到账接口举例说明,如果确定了要传递参数是partner、_input_charset、sign、sign_type、service、notify_url、return_url、total_fee、subject、out_trade_no、payment_type、seller_email。 那么还需要获得这些参数对应的值,把参数及其对应的值集合到数组或集合中。 开发语言java为例: Map<String, String> sParaTemp = new HashMap<String, String>(); sParaTemp.put("service", "create_direct_pay_by_user"); sParaTemp.put("partner", "2088501624560335"); sParaTemp.put("_input_charset", "utf-8"); sParaTemp.put("payment_type", "1"); sParaTemp.put("notify_url", "http://商户网关地址/alipay/notify_url.jsp"); sParaTemp.put("return_url", "http://商户网关地址/alipay/return_url.jsp"); sParaTemp.put("seller_email", "alipayrisk10@alipay.com"); sParaTemp.put("out_trade_no", "2012113000001"); sParaTemp.put("subject", "测试订单"); sParaTemp.put("total_fee", "0.01"); sParaTemp.put("sign", "此时是不知道的"); sParaTemp.put("sign_type", "MD5"); 注意: ①参数对应的这些值一般都是通过商户端的订单系统、支付宝配置信息中动态获得。 ②这些参数没有被编码过,是原始数据。 ③这些参数的值,不能存在影响接口逻辑的特殊字符,例如:&、/、^、%、@、=、+等。
签名及拿到签名结果 1、组装待签名字符串 1)筛选 大部分支付宝接口中要剔除sign_type、sign两个参数,个别接口只剔除sign参数。 存在空值的参数必须剔除。 如: Map<String, String> sParaTemp = new HashMap<String, String>(); sParaTemp.put("service", "create_direct_pay_by_user"); sParaTemp.put("partner", "2088501624560335"); sParaTemp.put("_input_charset", "utf-8"); sParaTemp.put("payment_type", "1"); sParaTemp.put("notify_url", "http://商户网关地址/alipay/notify_url.jsp"); sParaTemp.put("return_url", "http://商户网关地址/alipay/return_url.jsp"); sParaTemp.put("seller_email", "alipayrisk10@alipay.com"); sParaTemp.put("out_trade_no", "2012113000001"); sParaTemp.put("subject", "测试订单"); sParaTemp.put("total_fee", "0.01"); 2)排序 在参数集合中,根据参数(不是参数对应的值)的第一个字符的键值ASCII码递增排序,如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。 如: Map<String, String> sParaTemp = new HashMap<String, String>(); sParaTemp.put("_input_charset", "utf-8"); sParaTemp.put("notify_url", "http://商户网关地址/alipay/notify_url.jsp"); sParaTemp.put("out_trade_no", "2012113000001"); sParaTemp.put("partner", "2088501624560335"); sParaTemp.put("payment_type", "1"); sParaTemp.put("return_url", "http://商户网关地址/alipay/return_url.jsp"); sParaTemp.put("seller_email", "alipayrisk10@alipay.com"); sParaTemp.put("service", "create_direct_pay_by_user"); sParaTemp.put("subject", "测试订单"); sParaTemp.put("total_fee", "0.01"); 3)拼接 在参数集合中,把每个参数及其值组合成“参数=参数值”的格式(在无线产品手机安全支付中,每个参数的组合格式是“参数="参数值"”),并且把这些参数用&字符连接起来,如: _input_charset=utf-8¬ify_url=http://商户网关地址/alipay/notify_url.jsp&out_trade_no=2012113000001&partner= 2088501624560335&payment_type=1&return_url=http://商户网关地址/alipay/return_url.jsp&seller_email=alipayrisk10@alipay.com& service=create_direct_pay_by_user&subject=测试订单&total_fee=0.01 2、调用签名函数 1)MD5签名 密钥获取: 查看MD5(Key) 密钥用法: 把MD5密钥(Key)拼接在待签名字符串尾部。 签名函数 调用md5加密函数,对已经与MD5密钥拼接好的新字符串做加密运算。 2)RSA或DSA签名 密钥生成: 请查看:PID和密钥管理中相应的内容 密钥用法: 准备好支付宝公钥、商户自己生成的私钥、待签名字符串。 签名函数 调用RSA或DSA加密函数,分别把商户自己生成的私钥、待签名字符串置入函数中得到签名结果。 3、签名结果的用途 得到的签名结果也是一串字符串,这串字符串便是sign参数的值,把这串字符串赋值于sign参数。
支付宝支持的传输方式是http协议,get或post传输。提供给商户代码demo中常用的两种传输方式:form表单提交、模拟远程http协议提交。
每个接口的传输方式有可能不同,例如:
请求支付宝以后,需要跳转到支付宝页面进行后续操作,则需要用到表单提交方式,而不是模拟远程http协议,如:即时到账接口等。
请求支付宝以后,不需要跳转到支付宝页面,直接与支付宝做交互得到返回结果即可,则需要用到模拟远程http协议提交,表单提交方式也是支持的,如:确认发货接口等。
请求支付宝时如果数据很多,则需要用到POST提交,如:即时到账批量退款接口。
1、确定请求的支付宝网关 大部分支付宝接口的网关是https://mapi.alipay.com/gateway.do,部分接口的网关是其他,需根据接口技术文档来设置。 注意:如果网关后面没有跟“?_input_charset=编码格式”,那么支付宝默认请求来的编码格式是gbk。因此建议不论是表单提交还是模拟远程http协议提交,请求地址必须是:https://mapi.alipay.com/gateway.do?_input_charset=编码格式 2、组装请求内容 遵守一个原则:有哪些参数参与了签名,那么请求时的参数就是哪些,并且加上sign、sign_type。 3、请求传输 如果是表单提交,则使用页面重定向功能,对当前页面做自动跳转;如果是远程模拟提交则需要使用各开发语言对应的函数功能运行加载。 注意: 1、页面重定向函数,不要使用window.open这类弹出窗口的JS语句、response.redrect等,因为支付宝系统会无法识别请求来源是哪个地址。 2、表单提交时需注意以下事项: 发送给支付宝的请求,如果使用 form 表单传输,需要按照以下要求编写: action的值必须为“https://mapi.alipay.com/gateway.do?_input_charset=该值”,如:https://mapi.alipay.com/gateway.do?_input_charset=utf-8 不允许写成完整的请求链接地址,即禁止https://mapi.alipay.com/gateway.do?后带有所有要请求给支付宝的请求参数数据; <form>与</form>之间需包含所有要请求给支付宝的参数,且每个参数的格式为<input type=“hidden” name=“参数名” value=“参数值” />; 在众多请求参数中,请求参数_input_charset(编码格式)必须存在于 form 表单中,即 form 表单中必须含有<input type=“hidden” name="_input_charset“ value=”参数值">; <form>与</form>之间包含的数据只允许是要请求给支付宝的参数,禁止出现商户自行命名,不在接口技术文档请求参数列表中的其他数据; form 表单的 method 属性,可自行选择 get、post两种。 不按照规范的后果: 请求支付宝时报错,错误码为 ILLEGAL_SIGN; 在 win7 系统下,如果浏览器是 IE8 以上,有可能出现发送请求链接时会无法跳转到支付宝,当前页面为空白页的情况。
上面仅仅是部分需要了解的使用要求,具体请看官方文档。
开发文档中写的很详细,以上部分仅仅描述了一点点本文对官方文档中的理解,如果你想用好支付宝,透彻的理解,那么就请仔细阅读官方文档吧,查看SDK源代码,进行实战代码。这里不再赘述官方文档的信息。
本文研究假设商户采用的是RSA秘钥签名方式,本文就是在此基础上进行研究。
下面我将重点详细描述一下如何在电商类App中使用支付宝SDK实现支付功能的。
首先对支付环境进行配置。
在拿到官方的demo之后,我们要摘取里面的一些组件,本文是针对支付宝SDK中的实例版本为: " 支付宝钱包支付接口开发包2.0标准版(20150917)" 的例子进行摘取。
我们需要摘取的文件有:AlipaySDK.bundle、AliPaySDK.framework、libcrypto.a、libssl.a、openssl文件夹、Order.h、order.m、Util文件夹。
其中 openssl文件夹 是用来对密文进行处理假如我们新建一个项目,在项目中我们要引入上面的摘取的文件。
引入上面摘取的一些组件之后再导入下面组件到我们的工程中去。导入后如下图:
导入上面的组件完后,这时候我们cmd + B 编译之后,会发现报红了,报了好多。下面我将一一的给出解决方案,如果你第一次做支付也没有关系,因为我就是这么一点一点的摸索出来的的。
首先看一下下面的报错截图如图1,针对第一个错误,我们点开错误,然后在定位错误的文件中引入 Foundation 框架(如图2),然后重新编译,效果如图(图3)所示。
图1
图 2
图 3
对于上面的凡是报错为 NSString 的错误,我们都可以像上面解决办法一样,点开错误,定位在报错的文件中,引入 Foundation 框架,然后再次编译,我突然想起来个办法,在引入完官方支付宝demo组件之后,可以在我们的工程中添加一个全局宏PCH文件,在文件中引入 Foundation 框架,这样一次编译就不会出现上面的错误红,但是会报如此下错误,即便不用这一个新方法,那么做到这里也会出现下面的错误(编译的时候头文件没有被发现),截图如下:
解决办法:在Buid Settings -> 搜索 Search Parths 在Hearder Search Paths 中添加 $(SRCROOT)/你项目的名字
如图:
如果再次编译仍旧报同样的错误,说明了还是,路径问题,这时候你需要再次打开上面的图片,检查一下里面是不是路径中项目名字重复两次我的就是,删掉一个重复的名字就可以了
好了,到这里,我们要使用支付宝支付的支付环境已经搭建完成,下面可以将主要的精力放在如何的实现支付上了。
下面是支付的是实现:
这里省略了如何去搭建UI,仅仅将支付的实现逻辑关键代码给出,支付写在一个支付的按钮点击事件中。
代码如下:
//支付宝支付的按钮 - (IBAction)payAction:(UIButton *)sender { NSString *partner = @"208850156608833063"; //商户的ID是唯一的,商户 parnter 合作身份者ID,以 2088 开头有 16 位纯数字组成的字符串,如 2088500045658827 //账户:ID seller,支付宝收款账号,手机号码或者邮箱格式 如: niuxingjian@yeah.net //公钥是用来解密的(因为App之间的跳转有空隙,有可能被截取数据,不安全) // NSString *seller = @"我是用户的银行账号"; NSString *privateKey = @"我是用户的私有秘钥,要通过openssl里的方法去生成私钥"; //私钥 //partner和seller获取失败,提示 if ([partner length] == 0 || [seller length] == 0 || [privateKey length] == 0) { NSLog(@"缺少partner或者seller或者私钥。"); return; } /* *生成订单信息及签名 */ //将商品信息赋予AlixPayOrder的成员变量 Order *order = [[Order alloc] init]; order.partner = partner; order.seller = seller; order.tradeNO = @"我是订单123456***"; //订单ID(由商家自行制定) order.productName = @"IOS支付宝支付技术实现"; //商品标题 order.productDescription = @"njw在学习iOS的支付技术"; //商品描述 order.amount = [NSString stringWithFormat:@"%.2f",118.2]; //商品价格 //******** order.notifyURL = @"http://app.商家.com/wash/unionpay/mobilentify"; //回调URL,告诉后台一些支付的结果信息,跟后台的服务器进行连接的 //以下的信息是支付的基本的配置信息(固定不变) order.service = @"mobile.securitypay.pay"; order.paymentType = @"1"; order.inputCharset = @"utf-8"; order.itBPay = @"30m"; order.showUrl = @"m.alipay.com"; // App 的标示,在支付宝点击返回的时候,能够回到我们的电商App //应用注册scheme,在AlixPayDemo-Info.plist定义URL types NSString *appScheme = @"njw"; //将商品信息拼接成字符串 NSString *orderSpec = [order description]; NSLog(@"orderSpec = %@",orderSpec); //获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode id<DataSigner> signer = CreateRSADataSigner(privateKey); NSString *signedString = [signer signString:orderSpec]; //将签名成功字符串格式化为订单字符串,请严格按照该格式 NSString *orderString = nil; if (signedString != nil) { orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"", orderSpec, signedString, @"RSA"];//这里采用 RSA 加密 //这就是支付宝给的回调信息 [[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) { NSLog(@"reslut = %@",resultDic); }]; } }
在Appdelegate.m文件中要填加的方法如下:
//添加方法 -(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{ [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) { NSLog(@"result = %@",resultDic);//返回的支付结果 //【由于在跳转支付宝客户端支付的过程中,商户 app 在后台很可能被系统 kill 了,所以 pay 接 口的 callback 就会失效,请商户对 standbyCallback 返回的回调结果进行处理,就是在这个方法 里面处理跟 callback 一样的逻辑】 }]; return YES; }
下面在项目的plist文件中作如下配置,保证我们在进行交易完成后从支付宝页面回调的时候能够返回到当前电商App:
图片信息说明:AppTransportSecuritySettings 这个字典是能够保证IOS9.0之后的系统能够正常的请求Https://.....的安全的超文本传输协议的请求
URLtypes 这个数组是能够确定我们的App 在完成支付后能够回到我们的电商App。
上面的使用环境配置,其实也可以不用这么麻烦,直接在PCH中导入一个宏定义,#import <Foundation/Foundation.h>,好像支付宝的demo中有一个全局的pch宏我们引入就可以,本文的是引入了以后还是不行,所以做了上面比较笨的方法,在报错的里面引入,然后对于路径的问题参照上面的解决办法,修改如:在Buid Settings -> 搜索 Search Parths 在Hearder Search Paths 中添加 $(SRCROOT)/你项目的名字即可。
实现效果:(注意为了保密一些我获得的私有partner 、praivatekey 等信息上面的代码全部给的假数据,我用真数据得到下面测试效果,成功实现支付,更新于16.01.12-中午12点)
Other:支付宝将采用大数据预测支付中的安全问题,通过对用户行为的分析进行建模,通过对用户账户的行为等数据进行多维度立体虚拟网络,能够很好的预先识别出账户的安全问题,很好减少账户资金的损失率。参考
个人觉得未来电子商务不仅仅仅限于国内,随着全球经济的发展,商品也要全球化,估计在一段时间内,有可能会慢慢的升温,产品的类别会受限于国家宏观调控。
关于支付宝中的一些使用我会在下一篇中给出博文。 参考: 基于IOS下的支付宝SDK的学习与使用——实现产品支付(二)