blob: 4e6b953096e2189f46539421dd6962a280e1d196 [file] [log] [blame]
// Copyright 2013 The Flutter 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:flutter/foundation.dart';
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import 'package:json_annotation/json_annotation.dart';
import 'billing_client_wrapper.dart';
import 'sku_details_wrapper.dart';
// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the
// below generated file. Run `flutter packages pub run build_runner watch` to
// rebuild and watch for further changes.
part 'purchase_wrapper.g.dart';
/// Data structure representing a successful purchase.
///
/// All purchase information should also be verified manually, with your
/// server if at all possible. See ["Verify a
/// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify).
///
/// This wraps [`com.android.billlingclient.api.Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase)
@JsonSerializable()
@PurchaseStateConverter()
@immutable
class PurchaseWrapper {
/// Creates a purchase wrapper with the given purchase details.
@visibleForTesting
const PurchaseWrapper({
required this.orderId,
required this.packageName,
required this.purchaseTime,
required this.purchaseToken,
required this.signature,
@Deprecated('Use skus instead') String? sku,
required this.skus,
required this.isAutoRenewing,
required this.originalJson,
this.developerPayload,
required this.isAcknowledged,
required this.purchaseState,
this.obfuscatedAccountId,
this.obfuscatedProfileId,
}) : _sku = sku;
/// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details.
factory PurchaseWrapper.fromJson(Map<String, dynamic> map) =>
_$PurchaseWrapperFromJson(map);
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is PurchaseWrapper &&
other.orderId == orderId &&
other.packageName == packageName &&
other.purchaseTime == purchaseTime &&
other.purchaseToken == purchaseToken &&
other.signature == signature &&
other.sku == sku &&
other.isAutoRenewing == isAutoRenewing &&
other.originalJson == originalJson &&
other.isAcknowledged == isAcknowledged &&
other.purchaseState == purchaseState;
}
@override
int get hashCode => Object.hash(
orderId,
packageName,
purchaseTime,
purchaseToken,
signature,
sku,
isAutoRenewing,
originalJson,
isAcknowledged,
purchaseState);
/// The unique ID for this purchase. Corresponds to the Google Payments order
/// ID.
@JsonKey(defaultValue: '')
final String orderId;
/// The package name the purchase was made from.
@JsonKey(defaultValue: '')
final String packageName;
/// When the purchase was made, as an epoch timestamp.
@JsonKey(defaultValue: 0)
final int purchaseTime;
/// A unique ID for a given [SkuDetailsWrapper], user, and purchase.
@JsonKey(defaultValue: '')
final String purchaseToken;
/// Signature of purchase data, signed with the developer's private key. Uses
/// RSASSA-PKCS1-v1_5.
@JsonKey(defaultValue: '')
final String signature;
/// The product ID of this purchase.
@Deprecated('Use skus instead')
@JsonKey(ignore: true)
String get sku => _sku ?? (skus.isNotEmpty ? skus.first : '');
final String? _sku;
/// The product IDs of this purchase.
@JsonKey(defaultValue: <String>[])
final List<String> skus;
/// True for subscriptions that renew automatically. Does not apply to
/// [SkuType.inapp] products.
///
/// For [SkuType.subs] this means that the subscription is canceled when it is
/// false.
///
/// The value is `false` for [SkuType.inapp] products.
final bool isAutoRenewing;
/// Details about this purchase, in JSON.
///
/// This can be used verify a purchase. See ["Verify a purchase on a
/// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device).
/// Note though that verifying a purchase locally is inherently insecure (see
/// the article for more details).
@JsonKey(defaultValue: '')
final String originalJson;
/// The payload specified by the developer when the purchase was acknowledged or consumed.
///
/// The value is `null` if it wasn't specified when the purchase was acknowledged or consumed.
/// The `developerPayload` is removed from [BillingClientWrapper.acknowledgePurchase], [BillingClientWrapper.consumeAsync], [InAppPurchaseConnection.completePurchase], [InAppPurchaseConnection.consumePurchase]
/// after plugin version `0.5.0`. As a result, this will be `null` for new purchases that happen after updating to `0.5.0`.
final String? developerPayload;
/// Whether the purchase has been acknowledged.
///
/// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase].
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
@JsonKey(defaultValue: false)
final bool isAcknowledged;
/// Determines the current state of the purchase.
///
/// [BillingClient.acknowledgePurchase] should only be called when the `purchaseState` is [PurchaseStateWrapper.purchased].
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
final PurchaseStateWrapper purchaseState;
/// The obfuscatedAccountId specified when making a purchase.
///
/// The [obfuscatedAccountId] can either be set in
/// [PurchaseParam.applicationUserName] when using the [InAppPurchasePlatform]
/// or by setting the [accountId] in [BillingClient.launchBillingFlow].
final String? obfuscatedAccountId;
/// The obfuscatedProfileId can be used when there are multiple profiles
/// withing one account. The obfuscatedProfileId should be specified when
/// making a purchase. This property can only be set on a purchase by
/// directly calling [BillingClient.launchBillingFlow] and is not available
/// on the generic [InAppPurchasePlatform].
final String? obfuscatedProfileId;
}
/// Data structure representing a purchase history record.
///
/// This class includes a subset of fields in [PurchaseWrapper].
///
/// This wraps [`com.android.billlingclient.api.PurchaseHistoryRecord`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord)
///
/// * See also: [BillingClient.queryPurchaseHistory] for obtaining a [PurchaseHistoryRecordWrapper].
// We can optionally make [PurchaseWrapper] extend or implement [PurchaseHistoryRecordWrapper].
// For now, we keep them separated classes to be consistent with Android's BillingClient implementation.
@JsonSerializable()
@immutable
class PurchaseHistoryRecordWrapper {
/// Creates a [PurchaseHistoryRecordWrapper] with the given record details.
@visibleForTesting
const PurchaseHistoryRecordWrapper({
required this.purchaseTime,
required this.purchaseToken,
required this.signature,
@Deprecated('Use skus instead') String? sku,
required this.skus,
required this.originalJson,
required this.developerPayload,
}) : _sku = sku;
/// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details.
factory PurchaseHistoryRecordWrapper.fromJson(Map<String, dynamic> map) =>
_$PurchaseHistoryRecordWrapperFromJson(map);
/// When the purchase was made, as an epoch timestamp.
@JsonKey(defaultValue: 0)
final int purchaseTime;
/// A unique ID for a given [SkuDetailsWrapper], user, and purchase.
@JsonKey(defaultValue: '')
final String purchaseToken;
/// Signature of purchase data, signed with the developer's private key. Uses
/// RSASSA-PKCS1-v1_5.
@JsonKey(defaultValue: '')
final String signature;
/// The product ID of this purchase.
@Deprecated('Use skus instead')
@JsonKey(ignore: true)
String get sku => _sku ?? (skus.isNotEmpty ? skus.first : '');
final String? _sku;
/// The product ID of this purchase.
@JsonKey(defaultValue: <String>[])
final List<String> skus;
/// Details about this purchase, in JSON.
///
/// This can be used verify a purchase. See ["Verify a purchase on a
/// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device).
/// Note though that verifying a purchase locally is inherently insecure (see
/// the article for more details).
@JsonKey(defaultValue: '')
final String originalJson;
/// The payload specified by the developer when the purchase was acknowledged or consumed.
///
/// The value is `null` if it wasn't specified when the purchase was acknowledged or consumed.
final String? developerPayload;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is PurchaseHistoryRecordWrapper &&
other.purchaseTime == purchaseTime &&
other.purchaseToken == purchaseToken &&
other.signature == signature &&
other.sku == sku &&
other.originalJson == originalJson &&
other.developerPayload == developerPayload;
}
@override
int get hashCode => Object.hash(purchaseTime, purchaseToken, signature, sku,
originalJson, developerPayload);
}
/// A data struct representing the result of a transaction.
///
/// Contains a potentially empty list of [PurchaseWrapper]s, a [BillingResultWrapper]
/// that contains a detailed description of the status and a
/// [BillingResponse] to signify the overall state of the transaction.
///
/// Wraps [`com.android.billingclient.api.Purchase.PurchasesResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult).
@JsonSerializable()
@BillingResponseConverter()
@immutable
class PurchasesResultWrapper {
/// Creates a [PurchasesResultWrapper] with the given purchase result details.
const PurchasesResultWrapper(
{required this.responseCode,
required this.billingResult,
required this.purchasesList});
/// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details.
factory PurchasesResultWrapper.fromJson(Map<String, dynamic> map) =>
_$PurchasesResultWrapperFromJson(map);
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is PurchasesResultWrapper &&
other.responseCode == responseCode &&
other.purchasesList == purchasesList &&
other.billingResult == billingResult;
}
@override
int get hashCode => Object.hash(billingResult, responseCode, purchasesList);
/// The detailed description of the status of the operation.
final BillingResultWrapper billingResult;
/// The status of the operation.
///
/// This can represent either the status of the "query purchase history" half
/// of the operation and the "user made purchases" transaction itself.
final BillingResponse responseCode;
/// The list of successful purchases made in this transaction.
///
/// May be empty, especially if [responseCode] is not [BillingResponse.ok].
@JsonKey(defaultValue: <PurchaseWrapper>[])
final List<PurchaseWrapper> purchasesList;
}
/// A data struct representing the result of a purchase history.
///
/// Contains a potentially empty list of [PurchaseHistoryRecordWrapper]s and a [BillingResultWrapper]
/// that contains a detailed description of the status.
@JsonSerializable()
@BillingResponseConverter()
@immutable
class PurchasesHistoryResult {
/// Creates a [PurchasesHistoryResult] with the provided history.
const PurchasesHistoryResult(
{required this.billingResult, required this.purchaseHistoryRecordList});
/// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details.
factory PurchasesHistoryResult.fromJson(Map<String, dynamic> map) =>
_$PurchasesHistoryResultFromJson(map);
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is PurchasesHistoryResult &&
other.purchaseHistoryRecordList == purchaseHistoryRecordList &&
other.billingResult == billingResult;
}
@override
int get hashCode => Object.hash(billingResult, purchaseHistoryRecordList);
/// The detailed description of the status of the [BillingClient.queryPurchaseHistory].
final BillingResultWrapper billingResult;
/// The list of queried purchase history records.
///
/// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok].
@JsonKey(defaultValue: <PurchaseHistoryRecordWrapper>[])
final List<PurchaseHistoryRecordWrapper> purchaseHistoryRecordList;
}
/// Possible state of a [PurchaseWrapper].
///
/// Wraps
/// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html).
/// * See also: [PurchaseWrapper].
@JsonEnum(alwaysCreate: true)
enum PurchaseStateWrapper {
/// The state is unspecified.
///
/// No actions on the [PurchaseWrapper] should be performed on this state.
/// This is a catch-all. It should never be returned by the Play Billing Library.
@JsonValue(0)
unspecified_state,
/// The user has completed the purchase process.
///
/// The production should be delivered and then the purchase should be acknowledged.
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
@JsonValue(1)
purchased,
/// The user has started the purchase process.
///
/// The user should follow the instructions that were given to them by the Play
/// Billing Library to complete the purchase.
///
/// You can also choose to remind the user to complete the purchase if you detected a
/// [PurchaseWrapper] is still in the `pending` state in the future while calling [BillingClient.queryPurchases].
@JsonValue(2)
pending,
}
/// Serializer for [PurchaseStateWrapper].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
/// `@PurchaseStateConverter()`.
class PurchaseStateConverter
implements JsonConverter<PurchaseStateWrapper, int?> {
/// Default const constructor.
const PurchaseStateConverter();
@override
PurchaseStateWrapper fromJson(int? json) {
if (json == null) {
return PurchaseStateWrapper.unspecified_state;
}
return $enumDecode(_$PurchaseStateWrapperEnumMap, json);
}
@override
int toJson(PurchaseStateWrapper object) =>
_$PurchaseStateWrapperEnumMap[object]!;
/// Converts the purchase state stored in `object` to a [PurchaseStatus].
///
/// [PurchaseStateWrapper.unspecified_state] is mapped to [PurchaseStatus.error].
PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) {
switch (object) {
case PurchaseStateWrapper.pending:
return PurchaseStatus.pending;
case PurchaseStateWrapper.purchased:
return PurchaseStatus.purchased;
case PurchaseStateWrapper.unspecified_state:
return PurchaseStatus.error;
}
}
}