接上一篇筆記
沃爾瑪賣一支牙膏的流程是: 1.把商家的牙膏放到貨柜上 2. 讓用戶自由選擇 3.用戶去收銀臺刷信用卡 4. 刷卡器交給用戶,等待銀行確認刷卡信息,如果返回付款確認信息,讓用戶拿走牙膏。
內置付費已經走完了前面的三步,用戶要一手交錢了,我們也要準備一手交貨嘍。(只收錢不辦事兒在App Store是行不通的,寫軟件易,建國家難,且寫且珍惜。)
這一步的流程圖如下:

處理支付信息 (Processes payment)
再回到沃爾瑪購買牙膏的場景,當刷信用卡的時候,整個操作流程大體如下:
對于銀聯的直聯商戶,流程如下:
1、刷卡信息(包括磁道和密碼)由POS機具受理后通過收單機構送往銀聯的收單系統。
2、銀聯收單系統將報文通過銀聯核心交換平臺送到信用卡的發卡銀行,根據交易指令,在發卡銀行的對應的卡片賬戶進行扣款。
3、銀聯核心交換系統收到扣款成功的返回后,將交易結果原路返回到POS終端上。
4、當天晚上11點,清算信息開始批量處理。
5、T+1日,各行在人行的頭寸賬戶根據銀聯的清算文件(指令)將資金進行劃撥,即交易資金從信用卡的發卡銀行轉移到商戶的收單銀行。
6、收單銀行將資金轉入商戶的具體清算賬戶(也可以由銀聯直接轉入)。
就扮演的角色而言,有持卡人、商戶、收單機構(為商戶提供服務的銀行或機構)、轉接清算機構(銀聯、VISA等卡組織)、發卡機構(信用卡銀行)
(以上答案為知乎網友周宇的解答,鏈接在此)
在內置付費購買環節中,App Store在此處也扮演了銀聯收單系統的角色,App Store會把扣款成功的信息返回給“售貨員”, 這里的“售貨員”是我們的一段代碼,名字叫做transaction queue observer。這個“售貨員”放在哪里有程序員自己來決定,大體上有兩個地方比較好:
-
對于非常小型的App, 可以放在 app delegate中
2.對大部分的Apps, 單獨弄一個類,和其它與Store有關的代碼放在一起就很不錯
這個名叫observer的"售貨員"必須要"簽署"SKPaymentTransactionObserver協議才能完成工作。- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
/ 放一個“售貨員” */
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
簽署了"SKPaymentTransactionObserver協議的“售貨員”必須遵從協議中的要求——執行paymentQueue:updatedTransactions:這個函數。工作的職責是: 當交易狀態(The Status of a Transaction)有任何的變化, 都要調用這個操作。操作的具體細節需要我們來完成。
交易的四種主要狀態以及采取相應的行動:
- SKPaymentTransactionStatePurchasing: 購買中,此時可更新UI來展現購買的過程
SKPaymentTransactionStateFailed: 購買錯誤,此時要根據錯誤的代碼給用戶相應的提示
SKPaymentTransactionStatePurchased: 購買成功,此時要提供給用戶相應的內容
-
SKPaymentTransactionStateRestored: 恢復已購產品,此時需要將已經購買的商品恢復給用戶
(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
// Call the appropriate custom method.
case SKPaymentTransactionStatePurchased: // 購買成功
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed: // 購買失敗
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: // 恢復已購
[self restoreTransaction:transaction];
default:
break;
}
}
}(void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSString * productIdentifier = transaction.payment.productIdentifier;
NSString * receipt = [transaction.transactionReceipt base64EncodedString];
if ([productIdentifier length] > 0) {
// 向自己的服務器驗證購買憑證
}
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}(void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"購買失敗");
} else {
NSLog(@"用戶取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}(void)restoreTransaction:(SKPaymentTransaction *)transaction {
// 恢復已經購買的產品
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
保存好購物憑證(Persisting the Purchase)
現實中,購物以后要給個發票或者購物小票。在這里,也需要這么做,永久存儲交易記錄。這樣做至少有兩個用處:
- 程序啟動以后,檢查購買記錄,讓已購的功能生效。
- 當用戶需要恢復已購功能的時候, 可以讀取這個記錄。
保存購物憑證的方法有如下幾種:
- 對于非消耗(non-consumable) 品, 并且iOS 7以上,可以使用app receipt來記錄
- 對于非消耗(non-consumable)品,但是是iOS7以下,可以使用User Defaults system 或者 iCloud來記錄
- 對于消耗品(consumable), 因為不能在不同設備上同步,因此不需要做永久記錄(有種強拆的感覺啊!)
將Value/Key保存在User Defaults 或者 iCloud中
#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
[storage setBool:YES forKey:@"enable_rocket_car"];
[storage setObject:@15 forKey:@"highest_unlocked_level"];
[storage synchronize];
將Receipt保存在User Defaults 或者 iCloud中
#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
NSData *newReceipt = transaction.transactionReceipt;
NSArray *savedReceipts = [storage arrayForKey:@"receipts"];
if (!receipts) {
// Storing the first receipt
[storage setObject:@[newReceipt] forKey:@"receipts"];
} else {
// Adding another receipt
NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:newReceipt];
[storage setObject:updatedReceipts forKey:@"receipts"];
}
[storage synchronize];
解鎖功能 Unlocking App Functionality
當用戶購買成功以后,就需要對相應的產品功能進行解鎖, 當使用Receipt的時候,代碼應該類似于下面的樣子
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
// Custom method to work with receipts
BOOL rocketCarEnabled = [self receipt:receiptData includesProductID:@"com.example.rocketCar"];
當使用Key:Value來存儲的時候, 代碼應該類似于下面的樣子:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];
在程序中寫下如下相應的代碼,判斷是否可以使用高級一點的功能 :)
if (rocketCarEnabled) {
// Use the rocket car.
} else {
// Use the regular car.
}
解鎖資源Delivering Associated Content
如果購買是有關資源的,比如更多的聲音,更多的圖片,更多的素材等等,可以有三種方式來處理這種情況:
- (Local Content) 內置一些熱門資源(預期會大賣的資源),不要太大,頂多幾M左右即可。
- (Apple-hosted Content) 使用Apple提供的Apple-hosted服務,這樣可以保證App的尺寸較為精簡。支持iOS 6以上。
- 使用自己的服務器。
結束交易 Finishing the Transaction
這里沒什么好講的,就是結束交易了。
SKPaymentTransaction *transaction = <# The current payment #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
需要注意的一點是,在交易結束之前,不要調用這個函數,會讓Apple-hosted Content沒法下載,因為在下載Apple-hosted內容之前,返回的transaction有一個SKDownload屬性,如果貿然調用了此函數,有可能會導致下載中斷,以及潛在的其它問題。