/// <summary> /// Initializes an async process to purchas the product. Only one purchase request can be happening at a time /// </summary> /// <param name="productId">Product to buy</param> /// <returns>Purchase object</returns> public async Task <InAppPurchaseResult> PurchaseAsync(string productId) { if (_billingClient == null || !_billingClient.IsReady) { throw new InAppPurchaseException(PurchaseError.DeveloperError, "Billing Client is not connected"); } // Make sure no purchases are being currently made if (_transactionPurchased != null && !_transactionPurchased.Task.IsCanceled) { throw new InAppPurchaseException(PurchaseError.DeveloperError, "Another Purchase is in progress"); } // First, get the SkuDetail SkuDetails sku; if (!_retrievedProducts.TryGetValue(productId, out sku)) { throw new InAppPurchaseException(PurchaseError.DeveloperError, $"Cannot find a retrieved Product with {productId} SKU. Products must be first queried from the Play Store"); } // Build FlowParam for the Purchase BillingFlowParams flowParams = BillingFlowParams.NewBuilder() .SetSkuDetails(sku) .Build(); // Set a new Task Source to wait for completion _transactionPurchased = new TaskCompletionSource <InAppPurchaseResult>(); Task <InAppPurchaseResult> taskPurchaseComplete = _transactionPurchased.Task; //_billingClient.QueryPurchaseHistoryAsync(BillingClient.SkuType.Inapp, this); // Initiate the Billing Process. BillingResult response = _billingClient.LaunchBillingFlow(_activity, flowParams); if (response.ResponseCode != BillingResponseCode.Ok) { // Reset the in-app-purchase flow _transactionPurchased?.TrySetCanceled(); _transactionPurchased = null; throw new InAppPurchaseException(response.ResponseCode.ToPurchaseError(), response.DebugMessage); } // Wait till the Task is complete (e.g. Succeeded or Failed - which will result in Exception) InAppPurchaseResult purchase = await taskPurchaseComplete; _transactionPurchased = null; return(purchase); }
public override void UpdatedTransactions(SKPaymentQueue queue, SKPaymentTransaction[] transactions) { Debug.WriteLine("UpdatedTransactions called..."); foreach (SKPaymentTransaction transaction in transactions) { if (transaction?.TransactionState == null) { break; } Debug.WriteLine($"Updated Transaction | {transaction.TransactionState}; ProductId = {transaction.Payment?.ProductIdentifier ?? string.Empty}"); switch (transaction.TransactionState) { case SKPaymentTransactionState.Restored: _restoredTransactions.Add(transaction); SKPaymentQueue.DefaultQueue.FinishTransaction(transaction); break; case SKPaymentTransactionState.Purchased: InAppPurchaseResult result = transaction.ToInAppPurchase(); // If _transactionPurchased Task is not set, then it means the user initiated the out of the app purchase process // e.g. purchasing IAP from the App Store if (_transactionPurchased == null) { Debug.WriteLine($"_transactionPurchased is null. Continue with the OnPurchasedOutOfApp call"); InAppPurchaseService.OnPurchasedOutOfApp.Invoke(result); } else { // Normal Purchase flow when the user clicks purchase button. Debug.WriteLine($"_transactionPurchased is NOT null. Continue with the normal flow"); _transactionPurchased?.TrySetResult(transaction.ToInAppPurchase()); // Reset the Task _transactionPurchased = null; } SKPaymentQueue.DefaultQueue.FinishTransaction(transaction); break; case SKPaymentTransactionState.Failed: PurchaseError?error = transaction?.Error?.ToPurchaseError(); string description = transaction?.Error?.LocalizedDescription ?? string.Empty; // Failed Transaction. Set the Exception to the Task, so the caller can react to the issue _transactionPurchased?.TrySetException( new InAppPurchaseException(error ?? PurchaseError.GeneralError, description)); // Reset the Task _transactionPurchased = null; SKPaymentQueue.DefaultQueue.FinishTransaction(transaction); break; case SKPaymentTransactionState.Purchasing: case SKPaymentTransactionState.Deferred: default: break; } } }