Apple 在審核我們的 App 的時候會看一個東西,那就是我們的 App 內提供的對外連結是否具有引導消費的功能,消費的項目如果被認定踩中了 App 內購買的類型,比如說我在我的 App 放了一個按鈕,按下去之後用瀏覽器開啟我準備好的網頁,使用者在網頁中可以付費升級專業版,這樣的話有極大的機率會被 Apple Reject,然後叫我們用他們家的 In-App Purchases
,不過實作上也不算太難。
建立 App 內購買項目
我們到 iTunes Connect 的「我的 App
」裡面找到我們的 App(沒有的話就建一個),在「功能
」->「App 內購買項目
」建立購買項目。
選擇項目類型
紅框內及價格為必填項目
建立好之後,就可以在清單內看到.。
建立繼承自 SKPaymentTransactionObserver 的類別
SKPaymentTransactionObserver
是 StoreKit 內建的一個抽象類別,繼承之後透過它我們可以取得交易結果,除此之外還額外定義了 TransactionCompleted
Event,目的是為了將整個非同步的機制轉換成 Task-based 的非同步機制。
internal class IOSTransactionObserver : SKPaymentTransactionObserver
{
public event Action<SKPaymentTransaction, bool> TransactionCompleted;
public override void UpdatedTransactions(SKPaymentQueue queue, SKPaymentTransaction[] transactions)
{
foreach (var transaction in transactions)
{
if (transaction?.TransactionState == null) break;
switch (transaction.TransactionState)
{
case SKPaymentTransactionState.Restored:
case SKPaymentTransactionState.Purchased:
this.TransactionCompleted?.Invoke(transaction, true);
SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);
break;
case SKPaymentTransactionState.Failed:
this.TransactionCompleted?.Invoke(transaction, false);
SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);
break;
default: break;
}
}
}
}
實作 IInAppPurchases 介面
IInAppPurchases
是我自己定義的介面,在 iOS 專案中去實作之後透過 Xamarin.Forms 的 Dependency 機制就可以取用了。
public class IOSInAppPurchases : IInAppPurchases, IDisposable
{
private IOSTransactionObserver transactionObserver;
public IOSInAppPurchases()
{
this.transactionObserver = new IOSTransactionObserver();
// 添加交易結果的 Observer
SKPaymentQueue.DefaultQueue.AddTransactionObserver(this.transactionObserver);
}
public async Task<PaymentTransaction> PurchaseAsync(string productId)
{
var paymentTrans = await this.Purchase(productId);
var reference = new DateTime(2001, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var purchase = new PaymentTransaction();
purchase.TransactionUtcDate = reference.AddSeconds(paymentTrans.TransactionDate.SecondsSinceReferenceDate);
purchase.Id = paymentTrans.TransactionIdentifier;
purchase.ProductId = paymentTrans.Payment?.ProductIdentifier ?? string.Empty;
purchase.State = paymentTrans.TransactionState.ToString();
purchase.PurchaseToken =
paymentTrans.TransactionReceipt?.GetBase64EncodedString(NSDataBase64EncodingOptions.None)
?? string.Empty;
return purchase;
}
public void Dispose()
{
if (this.transactionObserver != null)
{
// 移除交易結果的 Observer
SKPaymentQueue.DefaultQueue.RemoveTransactionObserver(this.transactionObserver);
this.transactionObserver.Dispose();
this.transactionObserver = null;
}
}
private Task<SKPaymentTransaction> Purchase(string productId)
{
var tcsTransaction = new TaskCompletionSource<SKPaymentTransaction>();
void Handler(SKPaymentTransaction trans, bool result)
{
if (trans?.Payment == null) return;
if (productId != trans.Payment.ProductIdentifier) return;
this.transactionObserver.TransactionCompleted -= Handler;
if (result)
{
tcsTransaction.TrySetResult(trans);
return;
}
var errorCode = trans.Error?.Code ?? -1;
var description = trans.Error?.LocalizedDescription ?? string.Empty;
tcsTransaction.TrySetException(new Exception($"交易失敗(errorCode: {errorCode})\r\n{description}"));
}
this.transactionObserver.TransactionCompleted += Handler;
// 購買
SKPaymentQueue.DefaultQueue.AddPayment(SKPayment.CreateFrom(productId));
return tcsTransaction.Task;
}
}
建立測試帳號用來測試
進入「使用者和職能
」之後,到「沙箱技術測試人員
」新增測試帳號,紅框必填。
測試購買
按下購買按鈕之後呼叫 IInAppPurchases.PurchaseAsync()
方法,把產品 ID
丟進去就可以了,記得將 Apple ID 切換成剛剛建立的測試帳號,我們來看測試結果。
參考資料
< Source Code >