最近開發(fā)一個項目,需要負責Apple Pay相關(guān)的業(yè)務,所以寫這一篇文章來學習和研究Apple Pay相關(guān)的一些內(nèi)容,那么,首先是這篇文章的目錄:
目錄
1.Apple Pay是什么?
2.Apple Pay和IAP的區(qū)別
3.IAP流程
4.如何在項目中接入Apple Pay?
5.Apple Pay的支付流程
6.Apple Pay內(nèi)部的業(yè)務邏輯(通用)
Apple Pay是什么?
在Apple Pay的發(fā)布會上,Eddy Cue表示,蘋果并沒有興趣建立一個收集用戶數(shù)據(jù)的業(yè)務,蘋果并不知道你購買了什么,不知道你是從哪里購買的,為了這個商品花了多少錢。
- 所以這也就是蘋果和支付寶,微信等最大的不同:Apple Pay并不會將資金存放在Apple Pay中。
- Apple Pay其實也就是相當于一個卡包,替你保存銀行卡的信息,只不過是將這個卡包虛擬化了而已,而且Apple Pay中存儲的銀行卡信息等都進行了加密,所以非常的安全(當然所有的安全都不會是絕對安全)
- Apple Pay中的
Pay業(yè)務并不是Apple自己的業(yè)務,Apple Pay本身只是一個第三方的橋梁:連接用戶和銀行。Pay業(yè)務只是銀行和Apple之間所合作的一個業(yè)務,它和銀行之間是強關(guān)聯(lián)的關(guān)系,和Apple之間是弱關(guān)聯(lián)的關(guān)系,沒有銀行也就沒有pay了,但是支付寶和微信就不一樣,用戶將資金放在支付寶和微信中,即便沒有銀行,也可以直接進行支付。
Apple Pay和IAP的區(qū)別
什么是IAP呢?其實IAP就是In App purchase,即應用內(nèi)購買,也就是江湖人稱的內(nèi)購,IAP(應用內(nèi)購買)是最常用的一種支付方式,屬于免費應用+應用內(nèi)購買的模式。IAP主要是應用(App)和App Store服務器之間進行信息的傳遞,用戶在APP內(nèi)部進行內(nèi)購操作相當于用戶購買了App Store中的某個商品,這是用戶和APP Store之間的交易,然后蘋果從交易中抽取30%,APP的所有者獲得70%。
Apple Pay則不然,Apple Pay實質(zhì)上就是等同于用戶使用銀行卡進行刷卡消費,Apple Pay就是一個卡包的作用,它建立的是用戶,銀行,商家之間的關(guān)系:用戶購買商家的商品進行消費的時候,實則是通過Apple Pay向銀行發(fā)送付款信息,然后銀行接受消息,進行付款交易,交易完成,用戶獲得商品,商家獲得money。
理解Apple Pay和應用內(nèi)支付之間的區(qū)別是非常重要的。Apple Pay用于銷售物理商品,比如食品雜貨、衣服和電器,也能用于支付俱樂部的會員資格、酒店預訂以及演出門票。另一方面,應用內(nèi)支付(IPA)只用于銷售虛擬物品,如你的App里的高級內(nèi)容,以及訂閱數(shù)字內(nèi)容。
PassKit框架為Apple Pay提供API,應用內(nèi)支付(IAP)的API則由StoreKit框架提供。
IAP流程
什么是IAP?全稱即In App Purchase,也就是我們所講的蘋果內(nèi)購,IAP的流程分為兩種,一種是直接使用Apple的服務器進行購買和驗證,另一種就是自己架設(shè)服務器進行驗證。由于國內(nèi)網(wǎng)絡(luò)連接Apple服務器驗證非常慢,而且也是為了防止黑客偽造購買憑證,通常都是選擇自己架設(shè)服務器進行驗證,那么在了解IAP之前,首先就應該要了解一些在IAP之中的理論詞語。
Store Kit
Store Kit代表App和App Store之間進行通信。程序?qū)腁pp Store接收那些你想要提供的產(chǎn)品的信息,并將它們顯示出來供用戶購買。
當用戶需要購買某件產(chǎn)品時,程序調(diào)用Store Kit來收集購買信息。Products
產(chǎn)品可以是任意一項你想要出售的特性。產(chǎn)品在iTunes Connect中被組織,這和你添加一個新的App是一樣的。支持的產(chǎn)品種類共有四種:
1.內(nèi)容型。包括電子書,電子雜志,照片,插圖,游戲關(guān)卡,游戲角色,和其他的數(shù)字內(nèi)容。
2.擴展功能。這些功能已經(jīng)包含在App內(nèi)部。在未購買之前被鎖定。例如,你可以在一個游戲程序中包含若干個小游戲,用戶可以分別來購買這些游戲。
3.服務。允許程序?qū)未畏帐召M。比如錄音服務。
4.訂閱。支持對內(nèi)容或服務的擴展訪問。例如,你的程序可以每周提供財務信息或游戲門戶網(wǎng)站的信息。應該設(shè)定一個合理的更新周期,以避免過于頻繁的
提示困擾用戶。
要記?。耗銓⒇撠煾櫽嗛喌倪^期信息,并且管理續(xù)費。App Store不會替你監(jiān)視訂閱的周期,也不提供自動收費的機制>
- 通過App Store注冊產(chǎn)品
每個你想要出售的產(chǎn)品都必須先要通過iTunes Connect在App Store注冊。你需要提供產(chǎn)品的名稱,描述和其他在程序中用到的元數(shù)據(jù)。而且需要為產(chǎn)品指定唯一的標識符。當你的程序利用Store Kit和App Store通信時,會使用產(chǎn)品標識來取回產(chǎn)品的信息。如果用戶購買某個商品時,程序可以用該標識來將產(chǎn)品標注為“已購買”。 - App Store產(chǎn)品種類簡化為以下三種
1.消耗性商品。 該類商品在需要時被單次購買。比如,單次服務。
2.非消耗性商品。 該類商品只需被某個用戶購買一次,一旦被購買,和該用戶iTunes 賬戶關(guān)聯(lián)的設(shè)備都可以使用此商品。Store Kit為在多個設(shè)備上重新存儲非消耗性商品提供了內(nèi)置的支持。
3.訂閱類。訂閱類商品擁有以上兩種類型的特性。和消耗性商品一樣,訂閱類商品可以被多次購買; 你可以在程序內(nèi)部加入自己的訂閱計劃更新機制。 另外,訂閱類商品必須提供給和某一用戶關(guān)聯(lián)的所有設(shè)備。In App Purchase期望訂閱類商品可以通過外部服務器交付。你必須為多個設(shè)備的訂閱服務提供相應的支持。
IAP流程之使用Apple服務器
這種模型,需要交付的產(chǎn)品已經(jīng)在程序內(nèi)部了。這種方式通常用在一些被鎖定的功能上。也可以用來交付在程序束(App Bundle)中的內(nèi)容。該方式的一個重要的優(yōu)點是你可以及時的給客戶交付產(chǎn)品,大多數(shù)的內(nèi)置產(chǎn)品應該為非消耗商品。
注意:In App Purchase不提供購買補丁的功能。 如果需要更改app的bundle,你必須向App Store提交新的app版本。
為了標識產(chǎn)品,程序要在bundle中存儲產(chǎn)品的標識符。內(nèi)置模式下,Apple建議使用plist來紀錄產(chǎn)品的標識符。 內(nèi)容類應用可以使用折衷方式很方便的添加新的內(nèi)容,而不改動程序本身。
當成功購買產(chǎn)品后,程序應將鎖定的功能解鎖,提供給用戶。 解鎖的最簡單方式是修改程序偏好設(shè)置(Application Preferences)。 當用戶備份手機數(shù)據(jù)的時候,程序偏好設(shè)置也會隨之備份。 程序可能需要建議用戶在購買產(chǎn)品后備份手機以免丟失購買的內(nèi)容。

- 程序通過bundle存儲的plist文件得到產(chǎn)品標識符的列表。
- 程序?qū)⒌玫降漠a(chǎn)品ID向App Store發(fā)送請求,確認產(chǎn)品的信息。
- App Store返回產(chǎn)品信息。
- 程序把返回的產(chǎn)品信息顯示給用戶(App的store界面)
- 用戶選擇某個產(chǎn)品
- 程序向App Store發(fā)送支付請求
- App Store處理支付請求并返回交易完成信息。
- App獲取信息并提供內(nèi)容給用戶。
程序過程
- IAP開發(fā)前的準備
** 第一步:創(chuàng)建APP內(nèi)購項目**
iTunesConnect是蘋果提供的一個平臺,主要提供APP發(fā)布和管理App的,最重要的功能是創(chuàng)建管理項目信息,項目付費產(chǎn)品(道具)管理、付費的測試賬號、提交App等等。
首先需要登錄iTunesConnect,在平臺上填寫APP的內(nèi)購產(chǎn)品的相關(guān)信息
注意:產(chǎn)品的buddle ID是不可變的,但是產(chǎn)品ID等信息后期是可以修改的,這里的Bundle ID一定要跟項目中的info.plist中的Bundle ID保證一致,一般的buddle ID 的格式如下:com.domainname.appname
(bundle ID可以翻譯成包ID,也可以叫APP ID 或應用ID,它是每一個ios應用的全球唯一標識。無論代碼怎么改,圖標和應用名稱怎么換,只要bundle id沒變,ios系統(tǒng)就認為這是同一個應用。每開發(fā)一個新應用,首先都需要到member center->identifier->APP IDS去創(chuàng)建一個bundle id)
(ios certificates就是證書。它的作用就是證明你的mac具有開發(fā)或發(fā)布某個開發(fā)者賬號下應用的權(quán)限。而且證書還分成兩種,一種是開發(fā)證書,也叫Development certificate; 另一種是發(fā)布證書或叫生產(chǎn)證書,英文名叫Production certificate)
第二步:添加沙盒測試賬號
在用戶和職能中選擇沙箱技術(shù)測試人員,然后添加上技術(shù)測試賬號,測試賬號可以填的較為隨意。
- IAP的流程圖

-
IAP開發(fā)的代碼過程
1.在工程中引入storekit.framework并且import <StoreKit/StoreKit.h>
2.獲得所有產(chǎn)品的Product ID,并且用常量存儲在本地,產(chǎn)品ID也可以由自己的服務器返回。
3.制作一個界面,展示所有的應用內(nèi)付費項目。這些應用內(nèi)付費項目的價格和介紹信息可以是自己的服務器返回。但如果是不帶服務器的單機游戲應用或工具類應用, 則可以通過向App Store查詢獲得。實際上向App Store查詢速度非常慢,通常需要2-3秒鐘,所以不建議這么做,最好還是搞個自己的服務器進行操作。
4.用戶點擊一個IAP項目的時候,首先詢問用戶是否允許應用購買
//點擊購買按鈕
- (void)clickPurcaseBtnAction
{
//點擊按鈕的時候判斷app是否允許apple支付
if ([SKPaymentQueue canMakePayments]) {
//請求蘋果后臺商品
[self requestProductData:_productID];
}
else
{
NSLog(@"用戶不允許應用內(nèi)購買");
}
}
5.通過自己獲得的產(chǎn)品ID向Apple store發(fā)出請求,產(chǎn)品ID確認成功的時候創(chuàng)建SKPayment實例,發(fā)起購買操作
//去蘋果服務器請求商品
-(void)requestProductData:(NSString *)type {
//根據(jù)商品ID查找商品信息
NSArray *product = [[NSArray alloc] initWithObjects:type, nil];
NSSet *nsset = [NSSet setWithArray:product];
//創(chuàng)建SKProductsRequest對象,用想要出售的商品的標識來初始化,然后附加上對應的委托對象。
//該請求的響應包含了可用商品的本地化信息。
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
6.查詢請求結(jié)束后會有相對應的回調(diào)(查詢成功或者查詢失?。?/p>
// SKProductsRequestDelegate
//查詢成功的回調(diào)
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response {
NSArray *product = response.products;
//invalidProductIdentifiers是不被App Store所識別的產(chǎn)品id字符串數(shù)組,通常為空
NSLog(@"產(chǎn)品Product ID:%@",response.invalidProductIdentifiers);
if(product == nil) {
return;
}
//數(shù)組的count代表回調(diào)的產(chǎn)品ID數(shù)組的長度
if (product.count == 0) {
NSLog(@"無法獲得產(chǎn)品信息,購買失敗");
return;
}
//SKProduct對象包含了在App Store上注冊的商品的本地化信息。
SKProduct *storeProduct = nil;
for (SKProduct *pro in product) {
if ([pro.productIdentifier isEqualToString:_productID]) {
storeProduct = pro;
}
}
if(storeProduct == nil) {
return;
}
//創(chuàng)建一個支付對象,并放到隊列中
self.g_payment = [SKMutablePayment paymentWithProduct:storeProduct];
//設(shè)置購買的數(shù)量
self.g_payment.quantity = 1;
[[SKPaymentQueue defaultQueue] addPayment:self.g_payment];
}
//查詢失敗的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
CTHLog(@"請求商品失敗%@", error);
}
7.用戶的交易有結(jié)果是會調(diào)用下方的交易回調(diào)函數(shù)
//交易有結(jié)果時會調(diào)用此回調(diào)函數(shù),SKPaymentTransactionObserver - 交易的回調(diào)
- (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: //最終狀態(tài)未確認
[self deferredTransaction:transaction];
break;
default:
break;
}
}
}
8.蘋果服務器驗證憑據(jù)
- (void)verifyReceipt {
//1.appStoreReceiptURL ios 7.0增加的,購買交易完成之后,會將憑據(jù)存在本地沙盒
NSURL *receiptLocal = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptLocal];
//2.本地憑據(jù)的解碼(base64解碼)
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\", \"password\" : \"aaaaaaaaa\"}", encodeStr];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
//2.組合要想服務器發(fā)送的請求:HTTPMethod,HTTPBody
NSURL *verifyUrl = [NSURL URLWithString:buyUrl];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:verifyUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
urlRequest.HTTPMethod = @"POST";
urlRequest.HTTPBody = payloadData;
//3.驗證結(jié)果
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *sessionDataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 官方驗證結(jié)果為空
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流程之自己架設(shè)服務器
使用這種方式,要提供另外的服務器將產(chǎn)品發(fā)送給程序。 服務器交付適用于訂閱、內(nèi)容類商品和服務,因為商品可以作為數(shù)據(jù)發(fā)送,而不需改動程序束。 例如,一個游戲提供的新的內(nèi)容(關(guān)卡等)。 Store Kit不會對服務器端的設(shè)計和交互做出定義,這方面工作需要你來完成。 而且,Store Kit不提供驗證用戶身份的機制,你需要來設(shè)計。 如果你的程序需要以上功能,例如,紀錄特定用戶的訂閱計劃, 你需要自己來設(shè)計和實現(xiàn)。
Apple建議在服務器端存儲產(chǎn)品標識,而不是將其存儲在plist中。這樣就可以在不升級程序的前提下添加新的產(chǎn)品。
在服務器模式下,你的程序?qū)@得交易(transaction)相關(guān)的信息,并將它發(fā)送給服務器。服務器可以驗證收到的數(shù)據(jù),并將其解碼已確定需要交付的內(nèi)容。

- 程序向服務器發(fā)送請求,獲得一份產(chǎn)品列表。
- 服務器返回包含產(chǎn)品標識符的列表。
- 程序向App Store發(fā)送請求,得到產(chǎn)品的信息。
- App Store返回產(chǎn)品信息。
- 程序把返回的產(chǎn)品信息顯示給用戶(App的store界面)
- 用戶選擇某個產(chǎn)品
- 程序向App Store發(fā)送支付請求
- App Store處理支付請求并返回交易完成信息。
- 程序從信息中獲得數(shù)據(jù),并發(fā)送至服務器。
- 服務器紀錄數(shù)據(jù),并進行審(我們的)查。
- 服務器將數(shù)據(jù)發(fā)給App Store來驗證該交易的有效性。
- App Store對收到的數(shù)據(jù)進行解析,返回該數(shù)據(jù)和說明其是否有效的標識。
- 服務器讀取返回的數(shù)據(jù),確定用戶購買的內(nèi)容。
- 服務器將購買的內(nèi)容傳遞給程序。
接下來對這第二條架設(shè)服務器這14條進行一個總結(jié)(上文所述已有總結(jié))
- 用戶進入購買虛擬物品頁面,App從后臺服務器獲取產(chǎn)品列表然后顯示給用戶
- 用戶點擊購買購買某一個虛擬物品,APP就發(fā)送該虛擬物品的productionIdentifier到Apple服務器
- Apple服務器根據(jù)APP發(fā)送過來的productionIdentifier返回相應的物品的信息(描述,價格等)
- 用戶點擊確認鍵購買該物品,購買請求發(fā)送到Apple服務器
- Apple服務器完成購買后,返回用戶一個完成購買的憑證
- APP發(fā)送這個憑證到后臺服務器驗證
- 后臺服務器把這個憑證發(fā)送到Apple驗證,Apple返回一個字段給后臺服務器表明該憑證是否有效
- 后臺服務器把驗證結(jié)果在發(fā)送到APP,APP根據(jù)驗證結(jié)果做相應的處理
如何在項目中接入Apple Pay?
為了使Apple Pay生效,除了PassKit框架之外,還需要:
- 建立一個擁有支付模塊或通道的賬戶(如果你沒有的話)
- 從Certificates, Identifiers & Profiles注冊一個商業(yè)標示符
- 提交一個證書簽名需求以獲得用于加密和解碼支付令牌的公開或私有密鑰
- 在你的App里包含Apple Pay的支持權(quán)限
以下是較為具體的代碼代碼流程:
Apple Pay接入詳細教程
Apple Pay接入文檔
Apple Pay的支付流程
Apple并不處理和付款相關(guān)的邏輯,它只是負責支付信息的傳遞。Apple通過Touch ID來驗證銀行卡持有者的身份,實際的扣款行為發(fā)生在銀聯(lián)端,接入了Apple Pay的商品(即App)組織好Apple返回的支付信息,向銀行發(fā)出扣款請求后,該筆交易才會真正發(fā)送扣款,所以APP本身是和銀聯(lián)進行結(jié)算,Apple Pay只不過是作為一種支付的渠道而已。


Apple Pay內(nèi)部的業(yè)務邏輯

Apple Pay在應用內(nèi)的支付流程如下:
- APP根據(jù)使用場景顯示Payment Sheet
- 用戶選擇需要支付的卡以及支付需要的個人信息后,進行指紋驗證,之后根據(jù)情況,有些銀行卡還需要輸入卡所對應的密碼(PIN碼)
- App將支付相關(guān)的信息發(fā)送到Apple的服務器,進行加密。然后通過回調(diào)函數(shù)將加密后的支付信息返回給相應的App
- APP在收到回調(diào)之后,將對應的信息發(fā)送到自己的服務器中
- 服務器在收到APP發(fā)送過來的支付信息后,將數(shù)據(jù)進行解密操作,提取其中需要的信息,組織銀行接口報文,調(diào)用銀行的接口,完成扣款。
注意點:
App 收到的 Payment sheet 回調(diào)信息中,包含了一個 PKPayment 的對象,該對象包含了所有跟 Apple Pay 支付相關(guān)所有信息。比如用戶的手機號或者收貨地址等等,其中最重要的就是 payment token,它的 paymentData 字段數(shù)據(jù)就是需要發(fā)送給服務器的內(nèi)容。用戶信息部分是明文的,而支付信息也就是 paymentData 部分則是被加密過的。
paymentData 的內(nèi)容是 Json 格式的二進制流,服務器在收到這個數(shù)據(jù)之后進行解析,其中的 header.wrappedKey 是使用非對稱加密算法加密過的對稱秘鑰。使用在蘋果開發(fā)者后臺配置 merchant 時的私鑰進行解密,會得到這個對稱秘鑰。然后用這個對稱秘鑰對 data 字段所包含的加密數(shù)據(jù)進行解密,可以得到 Apple 返回的與支付相關(guān)的信息。此支付信息是加密過的,包含了用戶支付的卡號和 PIN 碼等信息,理論上只有銀聯(lián)才能解析出來真正的內(nèi)容,我們作為商戶是看不到具體信息的。服務器端需要將這些解密過的信息組織成銀聯(lián)所需的報文內(nèi)容,然后調(diào)用銀聯(lián)的扣款接口,完成扣款。