整个支付逻辑分为两个部分
1.通过跳转支付宝APP完成支付
新版需要appID 和 privateKey
2.通过手机网页完成支付
和旧版一样需要pid appID 和 privateKey
对订单模型order的总结
订单模型中分为两个部分
商品模型
@interface BizContent : NSObject
// NOTE: (非必填项)商品描述
@property (nonatomic, copy) NSString *body;
// NOTE: 商品的标题/交易标题/订单标题/订单关键字等。
@property (nonatomic, copy) NSString *subject;
// NOTE: 商户网站唯一订单号
@property (nonatomic, copy) NSString *out_trade_no;
// NOTE: 该笔订单允许的最晚付款时间,逾期将关闭交易。
// 取值范围:1m~15d m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)
// 该参数数值不接受小数点, 如1.5h,可转换为90m。
@property (nonatomic, copy) NSString *timeout_express;
// NOTE: 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
@property (nonatomic, copy) NSString *total_amount;
// NOTE: 收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID (如 2088102147948060)
@property (nonatomic, copy) NSString *seller_id;
// NOTE: 销售产品码,商家和支付宝签约的产品码 (如 QUICK_MSECURITY_PAY)
@property (nonatomic, copy) NSString *product_code;
@end
订单模型
@interface Order : NSObject
// NOTE: 支付宝分配给开发者的应用ID(如2014072300007148)
@property (nonatomic, copy) NSString *app_id;
// NOTE: 支付接口名称
@property (nonatomic, copy) NSString *method;
// NOTE: (非必填项)仅支持JSON
@property (nonatomic, copy) NSString *format;
// NOTE: (非必填项)HTTP/HTTPS开头字符串
@property (nonatomic, copy) NSString *return_url;
// NOTE: 参数编码格式,如utf-8,gbk,gb2312等
@property (nonatomic, copy) NSString *charset;
// NOTE: 请求发送的时间,格式"yyyy-MM-dd HH:mm:ss"
@property (nonatomic, copy) NSString *timestamp;
// NOTE: 请求调用的接口版本,固定为:1.0
@property (nonatomic, copy) NSString *version;
// NOTE: (非必填项)支付宝服务器主动通知商户服务器里指定的页面http路径
@property (nonatomic, copy) NSString *notify_url;
// NOTE: (非必填项)商户授权令牌,通过该令牌来帮助商户发起请求,完成业务(如201510BBaabdb44d8fd04607abf8d5931ec75D84)
@property (nonatomic, copy) NSString *app_auth_token;
// NOTE: 具体业务请求数据
@property (nonatomic, strong) BizContent *biz_content;
// NOTE: 签名类型
@property (nonatomic, copy) NSString *sign_type;
/**
* 获取订单信息串
*
* @param bEncoded 订单信息串中的各个value是否encode
* 非encode订单信息串,用于生成签名
* encode订单信息串 + 签名,用于最终的支付请求订单信息串
*/
- (NSString *)orderInfoEncoded:(BOOL)bEncoded;
@end
快速完成支付功能步骤(通过跳转支付宝APP方式)(步骤一和二在服务器完成)
步骤1:生成订单信息. 调用order.m里的函数description将商品信息拼接成字符串作为待签名字符串,如:
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1","body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA×tamp=2016-07-28 20:36:11&version=1.0
步骤2:对订单信息签名. 使用类CreateRSADataSigner,调用signString签名函数做签名,如:
// 获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
id<DataSigner> signer = CreateRSADataSigner(privateKey);
NSString *signedString = [signer signString:authInfoStr];
步骤3:把签名结果赋值给参数sign,并把sign加入之前的待签名数组中,此时得到的便是要请求给支付宝的全部数据。
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1","body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA×tamp=2016-07-28 20:36:11&version=1.0&sign=*********
步骤4:调用(AlipaySDK *)defaultService类下面的支付接口函数,唤起支付宝支付页面。
(void)payOrder:(NSString *)orderStr
fromScheme:(NSString *)schemeStr
callback:(CompletionBlock)completionBlock
appScheme为app在info.plist注册的scheme。
步骤5:当这笔交易被买家支付成功后支付宝收银台上显示该笔交易成功,并提示用户“返回”。此时在APAppDelegate.m的 - (BOOL)application:(UIApplication )application openURL:(NSURL )url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 中调用获取返回数据的代码【iOS9.0以上(包括iOS9.0)需要在 - (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一样的逻辑】
}];
快速完成支付功能步骤(通过网页完成方式)
步骤1:生成APAuthV2Info对象, 将授权信息拼接成字符串作为待签名字符串,如:
//生成 auth info 对象
APAuthV2Info *authInfo = [APAuthV2Info new];
authInfo.pid = pid;
authInfo.appID = appID;
//auth type
NSString *authType = [[NSUserDefaults standardUserDefaults] objectForKey:@"authType"];
if (authType) {
authInfo.authType = authType;
}
//应用注册scheme,在AlixPayDemo-Info.plist定义URL types
NSString *appScheme = @"alisdkdemo";
// 将授权信息拼接成字符串
NSString *authInfoStr = [authInfo description];
NSLog(@"authInfoStr = %@",authInfoStr);
步骤2:使用类CreateRSADataSigner,调用signString签名函数做签名,如:
// 获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
id<DataSigner> signer = CreateRSADataSigner(privateKey);
NSString *signedString = [signer signString:authInfoStr];
步骤3:把签名结果赋值给参数sign,并把sign加入之前的待签名数组中,此时得到的便是要请求给支付宝的全部数据。
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1","body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA×tamp=2016-07-28 20:36:11&version=1.0&sign=*********
步骤4:调用(AlipaySDK *)defaultService类下面的支付接口函数,唤起支付宝支付页面。
// 将签名成功字符串格式化为订单字符串,请严格按照该格式
authInfoStr = [NSString stringWithFormat:@"%@&sign=%@&sign_type=%@", authInfoStr, signedString, @"RSA"];
[[AlipaySDK defaultService] auth_V2WithInfo:authInfoStr
fromScheme:appScheme
callback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
// 解析 auth code
NSString *result = resultDic[@"result"];
NSString *authCode = nil;
if (result.length>0) {
NSArray *resultArr = [result componentsSeparatedByString:@"&"];
for (NSString *subResult in resultArr) {
if (subResult.length > 10 && [subResult hasPrefix:@"auth_code="]) {
authCode = [subResult substringFromIndex:10];
break;
}
}
}
NSLog(@"授权结果 authCode = %@", authCode?:@"");
}];
步骤5:当这笔交易被买家支付成功后支付宝收银台上显示该笔交易成功,并提示用户“返回”。此时在APAppDelegate.m的 - (BOOL)application:(UIApplication )application openURL:(NSURL )url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 中调用获取返回数据的代码【iOS9.0以上(包括iOS9.0)需要在 - (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一样的逻辑】
}];
App支付iOS调用说明
快捷订单支付iOS
处理客户端返回url
回调接口
接口名称:AlipaySDK
接口描述:提供支付功能。
Alipay接口主要为商户提供订单支付功能。接口所提供的方法,如下表所示:
方法名称 方法描述
+(Alipay *)defaultService; 获取服务实例。
-(void)payOrder:(NSString *)orderStr fromScheme:(NSString *)schemeStr callback:(CompletionBlock)completionBlock; 支付并通过回调返回结果。
-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock; 处理支付宝客户端返回的url(在app被杀模式下,通过这个方法获取支付结果)。
快捷订单支付iOS
方法名称:pay方法
方法原型:(void)payOrder:(NSString *)orderStr fromScheme:(NSString *)schemeStr callback:(CompletionBlock)completionBlock;
方法功能:提供给商户快捷订单支付功能。
参数名称 参数描述
NSString* scheme 商户程序注册的URL protocol,供支付完成后回调商户程序使用。
(CompletionBlock)completionBlock 快捷支付开发包回调函数,返回免登、支付结果。本地未安装支付宝客户端,或未成功调用支付宝客户端进行支付的情况下(走H5收银台),会通过该completionBlock返回支付结果。相应的结果参考"客户端同步返回"。
NSString* orderString app支付请求参数字符串,主要包含商户的订单信息,key=value形式,以&连接。
orderStr示例如下,参数说明见"请求参数说明":
app_id=2015052600090779&biz_content=%7B%22timeout_express%22%3A%2230m%22%2C%22seller_id%22%3A%22%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22total_amount%22%3A%220.02%22%2C%22subject%22%3A%221%22%2C%22body%22%3A%22%E6%88%91%E6%98%AF%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%22%2C%22out_trade_no%22%3A%22314VYGIAGG7ZOYY%22%7D&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA×tamp=2016-08-15%2012%3A12%3A15&version=1.0&sign=MsbylYkCzlfYLy9PeRwUUIg9nZPeN9SfXPNavUCroGKR5Kqvx0nEnd3eRmKxJuthNUx4ERCXe552EV9PfwexqW%2B1wbKOdYtDIb4%2B7PL3Pc94RZL0zKaWcaY3tSL89%2FuAVUsQuFqEJdhIukuKygrXucvejOUgTCfoUdwTi7z%2BZzQ%3D
处理客户端返回url
方法名称:处理客户端方法
方法原型:-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock;
方法功能:设备已安装支付宝客户端情况下,处理支付宝客户端返回的url(在app被杀模式下,通过这个方法获取支付结果)。
注意:该方法必须实现,否则将会导致在安装手机支付宝的情况下,支付结果无法正常同步返回。
参数名称 参数描述
NSURL *resultUrl 支付宝客户端回传的url
CompletionBlock completionBlock 本地安装了支付宝客户端,且成功调用支付宝客户端进行支付的情况下,会通过该completionBlock返回支付结果
备注:请在APPDelegate的 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 中调用该方法,iOS9.0以上(包括iOS9.0)请在 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options 中调用该方法,具体可参见Demo。
回调接口
在支付过程结束后,会通过callbackBlock同步返回支付结果(callbackBlock是调用支付同步的回调)。支付结果中参数的提取,必须通过CompletionBlock获取,禁止开发者私自解析支付结果返回的URL,参数说明见"客户端同步返回"。
其他接入方式
概述
对比
如果您已经接入了手机网站支付,除了“集成流程详解”中介绍的接入方式,支付宝推荐另一种更为便利的SDK接入方式——手机网站支付转为Native支付。
概述
如果您已经接入支付宝手机网站支付,可以通过接入我们的SDK将手机网站支付转为Native支付。接入过程极其简单,只需拦截手机网站支付的url,将该url转交给SDK进行处理;无需接入者解析参数字段,接入者的服务端也无需改造。
为什么要将手机网站支付转为Native支付? Native支付的用户体验和支付成功率均优于手机网站支付。
对比
下面以淘宝为例对比手机网站支付和手机网站转Native支付的流程。
手机网站支付流程
步骤一: 在手机端浏览器中访问淘宝主页www.taobao.com
步骤二: 挑选商品并进行付款
步骤三: 点击“立即支付”进入付款详情页面(H5页面)
手机网站转Native支付流程
下载Demo,并将Demo App安装到手机上即可体验该流程,请确保手机上安装了支付宝App。
步骤一: 运行Demo,并在Demo中打开淘宝主页www.taobao.com
iOS: 点击URLPay->openUrl,输入www.taobao.com
Android: 点击网页支付转native
步骤二: 挑选商品并进行付款
iOS截图
Android截图
对比总结
主要区别是:如果用户手机安装了支付宝App,手机网站转Native支付方式会跳转到支付宝App中进行订单支付,用户体验和支付成功率均优于手机网站支付方式。除此之外,还能使用手机网站支付没有提供的功能,例如:指纹支付、手环、手表支付、免密支付等。
如果用户手机没有安装支付宝App怎么办? 如果用户手机没有安装支付宝App,将在SDK提供的WebView中打开H5页面进行支付。即便如此,由于SDK与服务端的交互携带账号信息,仍比不携带任何账号信息的普通手机网站支付体验更好。
如何实现手机网站转Native支付
要实现上述功能需接入我们提供的SDK。
接入过程十分简单,可以以上述Demo为参考,该Demo程序只有一个功能:创建一个WebView,在WebView中拦截每个URL,然后调用SDK提供的接口检查该URL是否是有效的支付宝订单支付URL,如果是则将该URL传给SDK提供的支付接口进行支付。
支付宝请求参数说明
接口功能:外部商户App唤起快捷SDK创建订单并支付。
请求参数是商户在与支付宝进行数据交互时,提供给支付宝的请求数据,以便支付宝根据这些数据进一步处理。
公共参数 -- order
参数 类型 是否必填 最大长度 描述 示例值
app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
method String 是 128 接口名称 alipay.trade.app.pay
format String 否 40 仅支持JSON JSON
charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA RSA
sign String 是 256 商户请求参数的签名串,详见签名 详见示例
timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
version String 是 3 调用的接口版本,固定为:1.0 1.0
notify_url String 是 256 支付宝服务器主动通知商户服务器里指定的页面http/https路径。建议商户使用https https://api.xx.com/receive_notify.htm
biz_content String 是 - 业务请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
业务参数 -- biz_content
参数 类型 是否必填 最大长度 描述 示例值
body String 否 128 对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。 Iphone6 16G
subject String 是 256 商品的标题/交易标题/订单标题/订单关键字等。 大乐透
out_trade_no String 是 64 商户网站唯一订单号 70501111111S001111119
timeout_express String 否 6 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 90m
total_amount String 是 9 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000] 9.00
seller_id String 否 16 收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID 2088102147948060
product_code String 是 64 销售产品码,商家和支付宝签约的产品码 QUICK_MSECURITY_PAY
请求示例
请求参数组装分下列3步,以最后第三步获取到的请求为准
- 请求参数按照key=value&key=value方式拼接的未签名原始字符串:
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.01","subject":"1","body":"我是测试数据","out_trade_no":"IQJZSRC1YMQB5HU"}&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http://domain.merchant.com/payment_notify&sign_type=RSA×tamp=2016-08-25 20:26:31&version=1.0
- 再对原始字符串进行签名,参考 签名规则:
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.01","subject":"1","body":"我是测试数据","out_trade_no":"IQJZSRC1YMQB5HU"}&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http://domain.merchant.com/payment_notify&sign_type=RSA×tamp=2016-08-25 20:26:31&version=1.0&sign=cYmuUnKi5QdBsoZEAbMXVMmRWjsuUj+y48A2DvWAVVBuYkiBj13CFDHu2vZQvmOfkjE0YqCUQE04kqm9Xg3tIX8tPeIGIFtsIyp/M45w1ZsDOiduBbduGfRo1XRsvAyVAv2hCrBLLrDI5Vi7uZZ77Lo5J0PpUUWwyQGt0M4cj8g=
- 最后对请求字符串的所有一级value(biz_content作为一个value)进行encode,编码格式按请求串中的charset为准,没传charset按UTF-8处理,获得最终的请求字符串:
app_id=2015052600090779&biz_content=%7B%22timeout_express%22%3A%2230m%22%2C%22seller_id%22%3A%22%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%221%22%2C%22body%22%3A%22%E6%88%91%E6%98%AF%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%22%2C%22out_trade_no%22%3A%22IQJZSRC1YMQB5HU%22%7D&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http%3A%2F%2Fdomain.merchant.com%2Fpayment_notify&sign_type=RSA×tamp=2016-08-25%2020%3A26%3A31&version=1.0&sign=cYmuUnKi5QdBsoZEAbMXVMmRWjsuUj%2By48A2DvWAVVBuYkiBj13CFDHu2vZQvmOfkjE0YqCUQE04kqm9Xg3tIX8tPeIGIFtsIyp%2FM45w1ZsDOiduBbduGfRo1XRsvAyVAv2hCrBLLrDI5Vi7uZZ77Lo5J0PpUUWwyQGt0M4cj8g%3D
特殊说明(很重要)
- 商户在请求参数中,自己附属的一些额外参数,不要和支付宝系统中约定的key(下表中 公共请求参数\请求参数)重名,否则将可能导致未知的异常。
比如以下示例中app_id=2014072300007148******&version=1.0&biz_content的key是公共请求参数,业务方自己的扩展参数需要放在biz_content内部,比如示例中tips属性,很显然下面total_amount属性是商户按照自己的业务属性赋值的,但是由于total_amount也是支付宝关键key,支付宝将会认为这个total_amount是支付宝业务的参数应该是金额,这个最终将导致误解析。下列请求串为了展示清晰,未进行encode并且做了格式化处理,下同。
app_id=2014072300007148&charset=UTF-8&version=1.0×tamp=2016-07-01 08:08:08&method=alipay.trade.wap.pay¬ify_url=https://api.**.com/pay_receive_notify.html&sign_type=RSA&sign=ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE&version=1.0&biz_content=
{
"body":"对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。",
"subject":"大乐透",
"out_trade_no":"70501111111S001111119",
"timeout_express":"90m",
"total_amount":"一共花费了10元",
"seller_id":"2088102147948060",
"auth_token":"appopenBb64d181d0146481ab6a762c00714cC27",
"product_code":"QUICK_MSECURITY_PAY",
"tips":"测试一笔支付"
}
- 商户的请求参数中,所有的key(支付宝关键key或者商户自己的key),其对应的value中都不应该出现支付宝关键key,否则该类交易将可能被支付宝拦截禁止支付。
比如以下的请求中"subject":"大乐透 这个辣条不错 out_trade_no=123 total_fee=123.5",其value值中有支付宝关键key"out_trade_no"、"total_fee",这样的业务请求参数支付宝将会拦截。
app_id=2014072300007148&charset=UTF-8&version=1.0×tamp=2016-07-01 08:08:08&method=alipay.trade.wap.pay¬ify_url=https://api.**.com/pay_receive_notify.htm&sign_type=RSA&sign=ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE&version=1.0&biz_content=
{
"body":"对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。",
"subject":"大乐透 这个辣条不错 out_trade_no=123 total_fee=123.5",
"out_trade_no":"70501111111S001111119",
"timeout_express":"90m",
"total_amount":10.0,
"seller_id":"2088102147948060",
"auth_token":"appopenBb64d181d0146481ab6a762c00714cC27",
"product_code":"QUICK_MSECURITY_PAY"
}
- 商户支付请求参数的安全注意点:
a)请求参数的sign字段请务必在服务端完成签名生成(不要在客户端本地签名);
b)支付请求中的订单金额total_amount,请务必依赖服务端,不要轻信客户端上行的数据(客户端本地上行数据在用户手机环境中无法确保一定安全)