blob: c211d2a4cdb886ca55806ffbc92a1378e52c9183 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:in_app_purchase/src/billing_client_wrappers/enum_converters.dart';
import 'package:in_app_purchase/src/billing_client_wrappers/purchase_wrapper.dart';
import 'package:in_app_purchase/src/store_kit_wrappers/enum_converters.dart';
import 'package:in_app_purchase/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart';
import './in_app_purchase_connection.dart';
import './product_details.dart';
/// [IAPError.code] code for failed purchases.
final String kPurchaseErrorCode = 'purchase_error';
/// [IAPError.code] code used when a query for previouys transaction has failed.
final String kRestoredPurchaseErrorCode = 'restore_transactions_failed';
/// [IAPError.code] code used when a consuming a purchased item fails.
final String kConsumptionFailedErrorCode = 'consume_purchase_failed';
final String _kPlatformIOS = 'ios';
final String _kPlatformAndroid = 'android';
/// Represents the data that is used to verify purchases.
///
/// The property [source] helps you to determine the method to verify purchases.
/// Different source of purchase has different methods of verifying purchases.
///
/// Both platforms have 2 ways to verify purchase data. You can either choose to verify the data locally using [localVerificationData]
/// or verify the data using your own server with [serverVerificationData].
///
/// For details on how to verify your purchase on iOS,
/// you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1).
///
/// On Android, all purchase information should also be verified manually. See [`Verify a purchase`](https://developer.android.com/google/play/billing/billing_library_overview#Verify).
///
/// It is preferable to verify purchases using a server with [serverVerificationData].
///
/// If the platform is iOS, it is possible the data can be null or your validation of this data turns out invalid. When this happens,
/// Call [InAppPurchaseConnection.refreshPurchaseVerificationData] to get a new [PurchaseVerificationData] object. And then you can
/// validate the receipt data again using one of the methods mentioned in [`Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1).
///
/// You should never use any purchase data until verified.
class PurchaseVerificationData {
/// The data used for local verification.
///
/// If the [source] is [IAPSource.AppStore], this data is a based64 encoded string. The structure of the payload is defined using ASN.1.
/// If the [source] is [IAPSource.GooglePlay], this data is a JSON String.
final String localVerificationData;
/// The data used for server verification.
///
/// If the platform is iOS, this data is identical to [localVerificationData].
final String serverVerificationData;
/// Indicates the source of the purchase.
final IAPSource source;
/// Creates a [PurchaseVerificationData] object with the provided information.
PurchaseVerificationData(
{required this.localVerificationData,
required this.serverVerificationData,
required this.source});
}
/// Status for a [PurchaseDetails].
///
/// This is the type for [PurchaseDetails.status].
enum PurchaseStatus {
/// The purchase process is pending.
///
/// You can update UI to let your users know the purchase is pending.
pending,
/// The purchase is finished and successful.
///
/// Update your UI to indicate the purchase is finished and deliver the product.
/// On Android, the google play store is handling the purchase, so we set the status to
/// `purchased` as long as we can successfully launch play store purchase flow.
purchased,
/// Some error occurred in the purchase. The purchasing process if aborted.
error
}
/// The parameter object for generating a purchase.
class PurchaseParam {
/// Creates a new purchase parameter object with the given data.
PurchaseParam(
{required this.productDetails,
this.applicationUserName,
this.sandboxTesting = false,
this.simulatesAskToBuyInSandbox = false});
/// The product to create payment for.
///
/// It has to match one of the valid [ProductDetails] objects that you get from [ProductDetailsResponse] after calling [InAppPurchaseConnection.queryProductDetails].
final ProductDetails productDetails;
/// An opaque id for the user's account that's unique to your app. (Optional)
///
/// Used to help the store detect irregular activity.
/// Do not pass in a clear text, your developer ID, the user’s Apple ID, or the
/// user's Google ID for this field.
/// For example, you can use a one-way hash of the user’s account name on your server.
final String? applicationUserName;
/// @deprecated Use [simulatesAskToBuyInSandbox] instead.
///
/// Only available on iOS, set it to `true` to produce an "ask to buy" flow for this payment in the sandbox.
///
/// See also [SKPaymentWrapper.simulatesAskToBuyInSandbox].
@deprecated
final bool sandboxTesting;
/// Only available on iOS, set it to `true` to produce an "ask to buy" flow for this payment in the sandbox.
///
/// See also [SKPaymentWrapper.simulatesAskToBuyInSandbox].
final bool simulatesAskToBuyInSandbox;
}
/// Represents the transaction details of a purchase.
///
/// This class unifies the BillingClient's [PurchaseWrapper] and StoreKit's [SKPaymentTransactionWrapper]. You can use the common attributes in
/// This class for simple operations. If you would like to see the detailed representation of the product, instead, use [PurchaseWrapper] on Android and [SKPaymentTransactionWrapper] on iOS.
class PurchaseDetails {
/// A unique identifier of the purchase.
///
/// The `value` is null on iOS if it is not a successful purchase.
final String? purchaseID;
/// The product identifier of the purchase.
final String productID;
/// The verification data of the purchase.
///
/// Use this to verify the purchase. See [PurchaseVerificationData] for
/// details on how to verify purchase use this data. You should never use any
/// purchase data until verified.
///
/// On iOS, [InAppPurchaseConnection.refreshPurchaseVerificationData] can be used to get a new
/// [PurchaseVerificationData] object for further validation.
final PurchaseVerificationData verificationData;
/// The timestamp of the transaction.
///
/// Milliseconds since epoch.
///
/// The value is `null` if [status] is not [PurchaseStatus.purchased].
final String? transactionDate;
/// The status that this [PurchaseDetails] is currently on.
PurchaseStatus get status => _status;
set status(PurchaseStatus status) {
if (_platform == _kPlatformIOS) {
if (status == PurchaseStatus.purchased ||
status == PurchaseStatus.error) {
_pendingCompletePurchase = true;
}
}
if (_platform == _kPlatformAndroid) {
if (status == PurchaseStatus.purchased) {
_pendingCompletePurchase = true;
}
}
_status = status;
}
late PurchaseStatus _status;
/// The error details when the [status] is [PurchaseStatus.error].
///
/// The value is `null` if [status] is not [PurchaseStatus.error].
IAPError? error;
/// Points back to the `StoreKits`'s [SKPaymentTransactionWrapper] object that generated this [PurchaseDetails] object.
///
/// This is `null` on Android.
final SKPaymentTransactionWrapper? skPaymentTransaction;
/// Points back to the `BillingClient`'s [PurchaseWrapper] object that generated this [PurchaseDetails] object.
///
/// This is `null` on iOS.
final PurchaseWrapper? billingClientPurchase;
/// The developer has to call [InAppPurchaseConnection.completePurchase] if the value is `true`
/// and the product has been delivered to the user.
///
/// The initial value is `false`.
/// * See also [InAppPurchaseConnection.completePurchase] for more details on completing purchases.
bool get pendingCompletePurchase => _pendingCompletePurchase;
bool _pendingCompletePurchase = false;
// The platform that the object is created on.
//
// The value is either '_kPlatformIOS' or '_kPlatformAndroid'.
String? _platform;
/// Creates a new PurchaseDetails object with the provided data.
PurchaseDetails({
this.purchaseID,
required this.productID,
required this.verificationData,
required this.transactionDate,
this.skPaymentTransaction,
this.billingClientPurchase,
});
/// Generate a [PurchaseDetails] object based on an iOS [SKTransactionWrapper] object.
PurchaseDetails.fromSKTransaction(
SKPaymentTransactionWrapper transaction, String base64EncodedReceipt)
: this.purchaseID = transaction.transactionIdentifier,
this.productID = transaction.payment.productIdentifier,
this.verificationData = PurchaseVerificationData(
localVerificationData: base64EncodedReceipt,
serverVerificationData: base64EncodedReceipt,
source: IAPSource.AppStore),
this.transactionDate = transaction.transactionTimeStamp != null
? (transaction.transactionTimeStamp! * 1000).toInt().toString()
: null,
this.skPaymentTransaction = transaction,
this.billingClientPurchase = null,
_platform = _kPlatformIOS {
status = SKTransactionStatusConverter()
.toPurchaseStatus(transaction.transactionState);
if (status == PurchaseStatus.error) {
error = IAPError(
source: IAPSource.AppStore,
code: kPurchaseErrorCode,
message: transaction.error?.domain ?? '',
details: transaction.error?.userInfo,
);
}
}
/// Generate a [PurchaseDetails] object based on an Android [Purchase] object.
PurchaseDetails.fromPurchase(PurchaseWrapper purchase)
: this.purchaseID = purchase.orderId,
this.productID = purchase.sku,
this.verificationData = PurchaseVerificationData(
localVerificationData: purchase.originalJson,
serverVerificationData: purchase.purchaseToken,
source: IAPSource.GooglePlay),
this.transactionDate = purchase.purchaseTime.toString(),
this.skPaymentTransaction = null,
this.billingClientPurchase = purchase,
_platform = _kPlatformAndroid {
status = PurchaseStateConverter().toPurchaseStatus(purchase.purchaseState);
if (status == PurchaseStatus.error) {
error = IAPError(
source: IAPSource.GooglePlay,
code: kPurchaseErrorCode,
message: '',
);
}
}
}
/// The response object for fetching the past purchases.
///
/// An instance of this class is returned in [InAppPurchaseConnection.queryPastPurchases].
class QueryPurchaseDetailsResponse {
/// Creates a new [QueryPurchaseDetailsResponse] object with the provider information.
QueryPurchaseDetailsResponse({required this.pastPurchases, this.error});
/// A list of successfully fetched past purchases.
///
/// If there are no past purchases, or there is an [error] fetching past purchases,
/// this variable is an empty List.
/// You should verify the purchase data using [PurchaseDetails.verificationData] before using the [PurchaseDetails] object.
final List<PurchaseDetails> pastPurchases;
/// The error when fetching past purchases.
///
/// If the fetch is successful, the value is `null`.
final IAPError? error;
}