最近开发一个项目,需要负责Apple Pay相关的业务,所以写这一篇文章来学习和研究Apple Pay相关的一些内容,那么,首先是这篇文章的目录:
目录
1.Apple Pay是什么?
2.Apple Pay和IAP的区别
3.IAP流程
4.如何在项目中接入Apple Pay?
5.Apple Pay的支付流程
6.Apple Pay内部的业务逻辑(通用)
Apple Pay是什么?
在Apple Pay的发布会上,Eddy Cue表示,苹果并没有兴趣建立一个收集用户数据的业务,苹果并不知道你购买了什么,不知道你是从哪里购买的,为了这个商品花了多少钱。
- 所以这也就是苹果和支付宝,微信等最大的不同:Apple Pay并不会将资金存放在Apple Pay中。
- Apple Pay其实也就是相当于一个卡包,替你保存银行卡的信息,只不过是将这个卡包虚拟化了而已,而且Apple Pay中存储的银行卡信息等都进行了加密,所以非常的安全(当然所有的安全都不会是绝对安全)
- Apple Pay中的
Pay
业务并不是Apple自己的业务,Apple Pay本身只是一个第三方的桥梁:连接用户和银行。Pay
业务只是银行和Apple之间所合作的一个业务,它和银行之间是强关联的关系,和Apple之间是弱关联的关系,没有银行也就没有pay了,但是支付宝和微信就不一样,用户将资金放在支付宝和微信中,即便没有银行,也可以直接进行支付。
Apple Pay和IAP的区别
什么是IAP呢?其实IAP就是In App purchase,即应用内购买,也就是江湖人称的内购,IAP(应用内购买)是最常用的一种支付方式,属于免费应用+应用内购买的模式。IAP主要是应用(App)和App Store服务器之间进行信息的传递,用户在APP内部进行内购操作相当于用户购买了App Store中的某个商品,这是用户和APP Store之间的交易,然后苹果从交易中抽取30%,APP的所有者获得70%。
Apple Pay则不然,Apple Pay实质上就是等同于用户使用银行卡进行刷卡消费,Apple Pay就是一个卡包的作用,它建立的是用户,银行,商家之间的关系:用户购买商家的商品进行消费的时候,实则是通过Apple Pay向银行发送付款信息,然后银行接受消息,进行付款交易,交易完成,用户获得商品,商家获得money。
理解Apple Pay和应用内支付之间的区别是非常重要的。Apple Pay用于销售物理商品,比如食品杂货、衣服和电器,也能用于支付俱乐部的会员资格、酒店预订以及演出门票。另一方面,应用内支付(IPA)只用于销售虚拟物品,如你的App里的高级内容,以及订阅数字内容。
PassKit框架为Apple Pay提供API,应用内支付(IAP)的API则由StoreKit框架提供。
IAP流程
什么是IAP?全称即In App Purchase,也就是我们所讲的苹果内购,IAP的流程分为两种,一种是直接使用Apple的服务器进行购买和验证,另一种就是自己架设服务器进行验证。由于国内网络连接Apple服务器验证非常慢,而且也是为了防止黑客伪造购买凭证,通常都是选择自己架设服务器进行验证,那么在了解IAP之前,首先就应该要了解一些在IAP之中的理论词语。
Store Kit
Store Kit代表App和App Store之间进行通信。程序将会从App Store接收那些你想要提供的产品的信息,并将它们显示出来供用户购买。
当用户需要购买某件产品时,程序调用Store Kit来收集购买信息。Products
产品可以是任意一项你想要出售的特性。产品在iTunes Connect中被组织,这和你添加一个新的App是一样的。支持的产品种类共有四种:
1.内容型。包括电子书,电子杂志,照片,插图,游戏关卡,游戏角色,和其他的数字内容。
2.扩展功能。这些功能已经包含在App内部。在未购买之前被锁定。例如,你可以在一个游戏程序中包含若干个小游戏,用户可以分别来购买这些游戏。
3.服务。允许程序对单次服务收费。比如录音服务。
4.订阅。支持对内容或服务的扩展访问。例如,你的程序可以每周提供财务信息或游戏门户网站的信息。应该设定一个合理的更新周期,以避免过于频繁的
提示困扰用户。
要记住:你将负责跟踪订阅的过期信息,并且管理续费。App Store不会替你监视订阅的周期,也不提供自动收费的机制>
- 通过App Store注册产品
每个你想要出售的产品都必须先要通过iTunes Connect在App Store注册。你需要提供产品的名称,描述和其他在程序中用到的元数据。而且需要为产品指定唯一的标识符。当你的程序利用Store Kit和App Store通信时,会使用产品标识来取回产品的信息。如果用户购买某个商品时,程序可以用该标识来将产品标注为“已购买”。 - App Store产品种类简化为以下三种
1.消耗性商品。 该类商品在需要时被单次购买。比如,单次服务。
2.非消耗性商品。 该类商品只需被某个用户购买一次,一旦被购买,和该用户iTunes 账户关联的设备都可以使用此商品。Store Kit为在多个设备上重新存储非消耗性商品提供了内置的支持。
3.订阅类。订阅类商品拥有以上两种类型的特性。和消耗性商品一样,订阅类商品可以被多次购买; 你可以在程序内部加入自己的订阅计划更新机制。 另外,订阅类商品必须提供给和某一用户关联的所有设备。In App Purchase期望订阅类商品可以通过外部服务器交付。你必须为多个设备的订阅服务提供相应的支持。
IAP流程之使用Apple服务器
这种模型,需要交付的产品已经在程序内部了。这种方式通常用在一些被锁定的功能上。也可以用来交付在程序束(App Bundle)中的内容。该方式的一个重要的优点是你可以及时的给客户交付产品,大多数的内置产品应该为非消耗商品。
注意:In App Purchase不提供购买补丁的功能。 如果需要更改app的bundle,你必须向App Store提交新的app版本。
为了标识产品,程序要在bundle中存储产品的标识符。内置模式下,Apple建议使用plist来纪录产品的标识符。 内容类应用可以使用折衷方式很方便的添加新的内容,而不改动程序本身。
当成功购买产品后,程序应将锁定的功能解锁,提供给用户。 解锁的最简单方式是修改程序偏好设置(Application Preferences)。 当用户备份手机数据的时候,程序偏好设置也会随之备份。 程序可能需要建议用户在购买产品后备份手机以免丢失购买的内容。
- 程序通过bundle存储的plist文件得到产品标识符的列表。
- 程序将得到的产品ID向App Store发送请求,确认产品的信息。
- App Store返回产品信息。
- 程序把返回的产品信息显示给用户(App的store界面)
- 用户选择某个产品
- 程序向App Store发送支付请求
- App Store处理支付请求并返回交易完成信息。
- App获取信息并提供内容给用户。
程序过程
- IAP开发前的准备
** 第一步:创建APP内购项目**
iTunesConnect是苹果提供的一个平台,主要提供APP发布和管理App的,最重要的功能是创建管理项目信息,项目付费产品(道具)管理、付费的测试账号、提交App等等。
首先需要登录iTunesConnect,在平台上填写APP的内购产品的相关信息
注意:产品的buddle ID是不可变的,但是产品ID等信息后期是可以修改的,这里的Bundle ID一定要跟项目中的info.plist中的Bundle ID保证一致,一般的buddle ID 的格式如下:com.domainname.appname
(bundle ID可以翻译成包ID,也可以叫APP ID 或应用ID,它是每一个ios应用的全球唯一标识。无论代码怎么改,图标和应用名称怎么换,只要bundle id没变,ios系统就认为这是同一个应用。每开发一个新应用,首先都需要到member center->identifier->APP IDS去创建一个bundle id)
(ios certificates就是证书。它的作用就是证明你的mac具有开发或发布某个开发者账号下应用的权限。而且证书还分成两种,一种是开发证书,也叫Development certificate; 另一种是发布证书或叫生产证书,英文名叫Production certificate)
第二步:添加沙盒测试账号
在用户和职能中选择沙箱技术测试人员,然后添加上技术测试账号,测试账号可以填的较为随意。
- IAP的流程图
-
IAP开发的代码过程
1.在工程中引入storekit.framework
并且import <StoreKit/StoreKit.h>
2.获得所有产品的Product ID
,并且用常量存储在本地,产品ID也可以由自己的服务器返回。
3.制作一个界面,展示所有的应用内付费项目。这些应用内付费项目的价格和介绍信息可以是自己的服务器返回。但如果是不带服务器的单机游戏应用或工具类应用, 则可以通过向App Store查询获得。实际上向App Store查询速度非常慢,通常需要2-3秒钟,所以不建议这么做,最好还是搞个自己的服务器进行操作。
4.用户点击一个IAP项目的时候,首先询问用户是否允许应用购买
//点击购买按钮
- (void)clickPurcaseBtnAction
{
//点击按钮的时候判断app是否允许apple支付
if ([SKPaymentQueue canMakePayments]) {
//请求苹果后台商品
[self requestProductData:_productID];
}
else
{
NSLog(@"用户不允许应用内购买");
}
}
5.通过自己获得的产品ID向Apple store发出请求,产品ID确认成功的时候创建SKPayment实例,发起购买操作
//去苹果服务器请求商品
-(void)requestProductData:(NSString *)type {
//根据商品ID查找商品信息
NSArray *product = [[NSArray alloc] initWithObjects:type, nil];
NSSet *nsset = [NSSet setWithArray:product];
//创建SKProductsRequest对象,用想要出售的商品的标识来初始化,然后附加上对应的委托对象。
//该请求的响应包含了可用商品的本地化信息。
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
6.查询请求结束后会有相对应的回调(查询成功或者查询失败)
// SKProductsRequestDelegate
//查询成功的回调
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response {
NSArray *product = response.products;
//invalidProductIdentifiers是不被App Store所识别的产品id字符串数组,通常为空
NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);
if(product == nil) {
return;
}
//数组的count代表回调的产品ID数组的长度
if (product.count == 0) {
NSLog(@"无法获得产品信息,购买失败");
return;
}
//SKProduct对象包含了在App Store上注册的商品的本地化信息。
SKProduct *storeProduct = nil;
for (SKProduct *pro in product) {
if ([pro.productIdentifier isEqualToString:_productID]) {
storeProduct = pro;
}
}
if(storeProduct == nil) {
return;
}
//创建一个支付对象,并放到队列中
self.g_payment = [SKMutablePayment paymentWithProduct:storeProduct];
//设置购买的数量
self.g_payment.quantity = 1;
[[SKPaymentQueue defaultQueue] addPayment:self.g_payment];
}
//查询失败的回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
CTHLog(@"请求商品失败%@", error);
}
7.用户的交易有结果是会调用下方的交易回调函数
//交易有结果时会调用此回调函数,SKPaymentTransactionObserver - 交易的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: //正在购买
[self purchasingTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased: //购买成功
[self completedTransaction:transaction Product:self.myProduct];
break;
case SKPaymentTransactionStateFailed: //购买失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: //恢复购买
[self restoredTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred: //最终状态未确认
[self deferredTransaction:transaction];
break;
default:
break;
}
}
}
8.苹果服务器验证凭据
- (void)verifyReceipt {
//1.appStoreReceiptURL ios 7.0增加的,购买交易完成之后,会将凭据存在本地沙盒
NSURL *receiptLocal = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptLocal];
//2.本地凭据的解码(base64解码)
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\", \"password\" : \"aaaaaaaaa\"}", encodeStr];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
//2.组合要想服务器发送的请求:HTTPMethod,HTTPBody
NSURL *verifyUrl = [NSURL URLWithString:buyUrl];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:verifyUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
urlRequest.HTTPMethod = @"POST";
urlRequest.HTTPBody = payloadData;
//3.验证结果
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *sessionDataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 官方验证结果为空
if (data == nil)
{
CTHLog(@"验证失败");
return;
}
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (dict != nil) {
//~验证成功
if ([dict[@"status"] intValue] == 0) {
}
else {
CTHLog(@"验证失败");
}
}
else {
CTHLog(@"验证失败");
}
}];
[sessionDataTask resume];
}
苹果测试的地址为: https://sandbox.itunes.apple.com/verifyReceipt
苹果正式购买的地址为:https://buy.itunes.apple.com/verifyReceipt
IAP流程之自己架设服务器
使用这种方式,要提供另外的服务器将产品发送给程序。 服务器交付适用于订阅、内容类商品和服务,因为商品可以作为数据发送,而不需改动程序束。 例如,一个游戏提供的新的内容(关卡等)。 Store Kit不会对服务器端的设计和交互做出定义,这方面工作需要你来完成。 而且,Store Kit不提供验证用户身份的机制,你需要来设计。 如果你的程序需要以上功能,例如,纪录特定用户的订阅计划, 你需要自己来设计和实现。
Apple建议在服务器端存储产品标识,而不是将其存储在plist中。这样就可以在不升级程序的前提下添加新的产品。
在服务器模式下,你的程序将获得交易(transaction)相关的信息,并将它发送给服务器。服务器可以验证收到的数据,并将其解码已确定需要交付的内容。
- 程序向服务器发送请求,获得一份产品列表。
- 服务器返回包含产品标识符的列表。
- 程序向App Store发送请求,得到产品的信息。
- App Store返回产品信息。
- 程序把返回的产品信息显示给用户(App的store界面)
- 用户选择某个产品
- 程序向App Store发送支付请求
- App Store处理支付请求并返回交易完成信息。
- 程序从信息中获得数据,并发送至服务器。
- 服务器纪录数据,并进行审(我们的)查。
- 服务器将数据发给App Store来验证该交易的有效性。
- App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。
- 服务器读取返回的数据,确定用户购买的内容。
- 服务器将购买的内容传递给程序。
接下来对这第二条架设服务器这14条进行一个总结(上文所述已有总结)
- 用户进入购买虚拟物品页面,App从后台服务器获取产品列表然后显示给用户
- 用户点击购买购买某一个虚拟物品,APP就发送该虚拟物品的productionIdentifier到Apple服务器
- Apple服务器根据APP发送过来的productionIdentifier返回相应的物品的信息(描述,价格等)
- 用户点击确认键购买该物品,购买请求发送到Apple服务器
- Apple服务器完成购买后,返回用户一个完成购买的凭证
- APP发送这个凭证到后台服务器验证
- 后台服务器把这个凭证发送到Apple验证,Apple返回一个字段给后台服务器表明该凭证是否有效
- 后台服务器把验证结果在发送到APP,APP根据验证结果做相应的处理
如何在项目中接入Apple Pay?
为了使Apple Pay生效,除了PassKit框架之外,还需要:
- 建立一个拥有支付模块或通道的账户(如果你没有的话)
- 从Certificates, Identifiers & Profiles注册一个商业标示符
- 提交一个证书签名需求以获得用于加密和解码支付令牌的公开或私有密钥
- 在你的App里包含Apple Pay的支持权限
以下是较为具体的代码代码流程:
Apple Pay接入详细教程
Apple Pay接入文档
Apple Pay的支付流程
Apple并不处理和付款相关的逻辑,它只是负责支付信息的传递。Apple通过Touch ID来验证银行卡持有者的身份,实际的扣款行为发生在银联端,接入了Apple Pay的商品(即App)组织好Apple返回的支付信息,向银行发出扣款请求后,该笔交易才会真正发送扣款,所以APP本身是和银联进行结算,Apple Pay只不过是作为一种支付的渠道而已。
Apple Pay内部的业务逻辑
Apple Pay在应用内的支付流程如下:
- APP根据使用场景显示Payment Sheet
- 用户选择需要支付的卡以及支付需要的个人信息后,进行指纹验证,之后根据情况,有些银行卡还需要输入卡所对应的密码(PIN码)
- App将支付相关的信息发送到Apple的服务器,进行加密。然后通过回调函数将加密后的支付信息返回给相应的App
- APP在收到回调之后,将对应的信息发送到自己的服务器中
- 服务器在收到APP发送过来的支付信息后,将数据进行解密操作,提取其中需要的信息,组织银行接口报文,调用银行的接口,完成扣款。
注意点:
App 收到的 Payment sheet 回调信息中,包含了一个 PKPayment 的对象,该对象包含了所有跟 Apple Pay 支付相关所有信息。比如用户的手机号或者收货地址等等,其中最重要的就是 payment token,它的 paymentData 字段数据就是需要发送给服务器的内容。用户信息部分是明文的,而支付信息也就是 paymentData 部分则是被加密过的。
paymentData 的内容是 Json 格式的二进制流,服务器在收到这个数据之后进行解析,其中的 header.wrappedKey 是使用非对称加密算法加密过的对称秘钥。使用在苹果开发者后台配置 merchant 时的私钥进行解密,会得到这个对称秘钥。然后用这个对称秘钥对 data 字段所包含的加密数据进行解密,可以得到 Apple 返回的与支付相关的信息。此支付信息是加密过的,包含了用户支付的卡号和 PIN 码等信息,理论上只有银联才能解析出来真正的内容,我们作为商户是看不到具体信息的。服务器端需要将这些解密过的信息组织成银联所需的报文内容,然后调用银联的扣款接口,完成扣款。