Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.6+1

* Refactors internals for improved testability.

## 0.4.6

* Adds a new case `.unverified` to enum `SK2ProductPurchaseResult`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2013 The Flutter Authors
// 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' show visibleForTesting;

import 'messages.g.dart';
import 'sk2_pigeon.g.dart';

/// The instance of the host API used to communicate with the platform side for
/// the original StoreKit API.
///
/// This is a global to allow tests to override the host API with a mock, since
/// in practice the host API is a singleton, and there is no way to inject it
/// in the usual way since many uses aren't in objects.
InAppPurchaseAPI hostApi = InAppPurchaseAPI();

/// The instance of the host API used to communicate with the platform side
/// for StoreKit2.
///
/// This is a global to allow tests to override the host API with a mock, since
/// in practice the host API is a singleton, and there is no way to inject it
/// in the usual way since many uses aren't in objects.
InAppPurchase2API hostApi2 = InAppPurchase2API();

/// Set up pigeon API.
@visibleForTesting
void setInAppPurchaseHostApis({
InAppPurchaseAPI? api,
InAppPurchase2API? api2,
}) {
if (api != null) {
hostApi = api;
}

if (api2 != null) {
hostApi2 = api2;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../store_kit_2_wrappers.dart';

InAppPurchase2API _hostApi = InAppPurchase2API();
import '../in_app_purchase_apis.dart';

/// Wrapper for StoreKit2's AppStore
/// (https://developer.apple.com/documentation/storekit/appstore)
Expand All @@ -13,14 +11,14 @@ final class AppStore {
/// Returns a bool that indicates whether the person can make purchases.
/// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
Future<bool> canMakePayments() {
return _hostApi.canMakePayments();
return hostApi2.canMakePayments();
}

/// Dart wrapper for StoreKit2's sync()
/// Synchronizes your app’s transaction information and subscription status with information from the App Store.
/// Will initiate an authentication pop up.
/// https://developer.apple.com/documentation/storekit/appstore/sync()
Future<void> sync() {
return _hostApi.sync();
return hostApi2.sync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import 'package:flutter/services.dart';

import '../../store_kit_2_wrappers.dart';

InAppPurchase2API _hostApi = InAppPurchase2API();
import '../in_app_purchase_apis.dart';

/// A wrapper around StoreKit2's ProductType
/// https://developer.apple.com/documentation/storekit/product/producttype
Expand Down Expand Up @@ -381,7 +380,7 @@ class SK2Product {
/// If any of the identifiers are invalid or can't be found, they are excluded
/// from the returned list.
static Future<List<SK2Product>> products(List<String> identifiers) async {
final List<SK2ProductMessage?> productsMsg = await _hostApi.products(
final List<SK2ProductMessage?> productsMsg = await hostApi2.products(
identifiers,
);
if (productsMsg.isEmpty && identifiers.isNotEmpty) {
Expand All @@ -406,17 +405,17 @@ class SK2Product {
}) async {
SK2ProductPurchaseResultMessage result;
if (options != null) {
result = await _hostApi.purchase(id, options: options.convertToPigeon());
result = await hostApi2.purchase(id, options: options.convertToPigeon());
} else {
result = await _hostApi.purchase(id);
result = await hostApi2.purchase(id);
}
return result.convertFromPigeon();
}

/// Checks if the user is eligible for an introductory offer.
/// The product must be an auto-renewable subscription.
static Future<bool> isIntroductoryOfferEligible(String productId) async {
final bool result = await _hostApi.isIntroductoryOfferEligible(productId);
final bool result = await hostApi2.isIntroductoryOfferEligible(productId);

return result;
}
Expand All @@ -426,7 +425,7 @@ class SK2Product {
String productId,
String offerId,
) async {
final bool result = await _hostApi.isWinBackOfferEligible(
final bool result = await hostApi2.isWinBackOfferEligible(
productId,
offerId,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../store_kit_2_wrappers.dart';

InAppPurchase2API _hostApi = InAppPurchase2API();
import '../in_app_purchase_apis.dart';

/// Wrapper for StoreKit2's Storefront
/// (https://developer.apple.com/documentation/storekit/storefront)
Expand All @@ -13,6 +11,6 @@ final class Storefront {
/// Returns the 3 letter code for a store's locale
/// (https://developer.apple.com/documentation/storekit/storefront/countrycode)
Future<String> countryCode() async {
return _hostApi.countryCode();
return hostApi2.countryCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte

import '../../in_app_purchase_storekit.dart';
import '../../store_kit_wrappers.dart';
import '../in_app_purchase_apis.dart';
import '../sk2_pigeon.g.dart';

InAppPurchase2API _hostApi = InAppPurchase2API();

/// Dart wrapper around StoreKit2's [Transaction](https://developer.apple.com/documentation/storekit/transaction)
/// Note that in StoreKit2, a Transaction encompasses the data contained by
/// SKPayment and SKTransaction in StoreKit1
Expand Down Expand Up @@ -72,14 +71,14 @@ class SK2Transaction {
/// Indicates to the App Store that the app delivered the purchased content
/// or enabled the service to finish the transaction.
static Future<void> finish(int id) async {
await _hostApi.finish(id);
await hostApi2.finish(id);
}

/// A wrapper around [Transaction.all]
/// https://developer.apple.com/documentation/storekit/transaction/3851203-all
/// A sequence that emits all the customer’s transactions for your app.
static Future<List<SK2Transaction>> transactions() async {
final List<SK2TransactionMessage> msgs = await _hostApi.transactions();
final List<SK2TransactionMessage> msgs = await hostApi2.transactions();
final List<SK2Transaction> transactions = msgs
.map((SK2TransactionMessage e) => e.convertFromPigeon())
.toList();
Expand All @@ -89,17 +88,17 @@ class SK2Transaction {
/// Start listening to transactions.
/// Call this as soon as you can your app to avoid missing transactions.
static void startListeningToTransactions() {
_hostApi.startListeningToTransactions();
hostApi2.startListeningToTransactions();
}

/// Stop listening to transactions.
static void stopListeningToTransactions() {
_hostApi.stopListeningToTransactions();
hostApi2.stopListeningToTransactions();
}

/// Restore previously completed purchases.
static Future<void> restorePurchases() async {
await _hostApi.restorePurchases();
await hostApi2.restorePurchases();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,12 @@ import 'package:json_annotation/json_annotation.dart';

import '../../store_kit_wrappers.dart';
import '../channel.dart';
import '../in_app_purchase_apis.dart';
import '../in_app_purchase_storekit_platform.dart';
import '../messages.g.dart';

part 'sk_payment_queue_wrapper.g.dart';

InAppPurchaseAPI _hostApi = InAppPurchaseAPI();

/// Set up pigeon API.
@visibleForTesting
void setInAppPurchaseHostApi(InAppPurchaseAPI api) {
_hostApi = api;
}

/// A wrapper around
/// [`SKPaymentQueue`](https://developer.apple.com/documentation/storekit/skpaymentqueue?language=objc).
///
Expand Down Expand Up @@ -54,12 +47,12 @@ class SKPaymentQueueWrapper {
///
/// Returns `null` if the user's device is below iOS 13.0 or macOS 10.15.
Future<SKStorefrontWrapper?> storefront() async {
return SKStorefrontWrapper.convertFromPigeon(await _hostApi.storefront());
return SKStorefrontWrapper.convertFromPigeon(await hostApi.storefront());
}

/// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc).
Future<List<SKPaymentTransactionWrapper>> transactions() async {
final List<SKPaymentTransactionMessage?> pigeonMsgs = await _hostApi
final List<SKPaymentTransactionMessage?> pigeonMsgs = await hostApi
.transactions();
return pigeonMsgs
.map(
Expand All @@ -70,7 +63,7 @@ class SKPaymentQueueWrapper {
}

/// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc).
static Future<bool> canMakePayments() async => _hostApi.canMakePayments();
static Future<bool> canMakePayments() async => hostApi.canMakePayments();

/// Sets an observer to listen to all incoming transaction events.
///
Expand All @@ -89,15 +82,15 @@ class SKPaymentQueueWrapper {
/// Call this method when the first listener is subscribed to the
/// [InAppPurchaseStoreKitPlatform.purchaseStream].
Future<void> startObservingTransactionQueue() =>
_hostApi.startObservingPaymentQueue();
hostApi.startObservingPaymentQueue();

/// Instructs the iOS implementation to remove the transaction observer and
/// stop listening to it.
///
/// Call this when there are no longer any listeners subscribed to the
/// [InAppPurchaseStoreKitPlatform.purchaseStream].
Future<void> stopObservingTransactionQueue() =>
_hostApi.stopObservingPaymentQueue();
hostApi.stopObservingPaymentQueue();

/// Sets an implementation of the [SKPaymentQueueDelegateWrapper].
///
Expand All @@ -111,10 +104,10 @@ class SKPaymentQueueWrapper {
/// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)).
Future<void> setDelegate(SKPaymentQueueDelegateWrapper? delegate) async {
if (delegate == null) {
await _hostApi.removePaymentQueueDelegate();
await hostApi.removePaymentQueueDelegate();
paymentQueueDelegateChannel.setMethodCallHandler(null);
} else {
await _hostApi.registerPaymentQueueDelegate();
await hostApi.registerPaymentQueueDelegate();
paymentQueueDelegateChannel.setMethodCallHandler(
handlePaymentQueueDelegateCallbacks,
);
Expand Down Expand Up @@ -149,7 +142,7 @@ class SKPaymentQueueWrapper {
'[in_app_purchase]: Trying to add a payment without an observer. One must be set using `SkPaymentQueueWrapper.setTransactionObserver` before the app launches.',
);

await _hostApi.addPayment(payment.toMap());
await hostApi.addPayment(payment.toMap());
}

/// Finishes a transaction and removes it from the queue.
Expand All @@ -167,7 +160,7 @@ class SKPaymentQueueWrapper {
SKPaymentTransactionWrapper transaction,
) async {
final Map<String, String?> requestMap = transaction.toFinishMap();
await _hostApi.finishTransaction(requestMap);
await hostApi.finishTransaction(requestMap);
}

/// Restore previously purchased transactions.
Expand All @@ -191,7 +184,7 @@ class SKPaymentQueueWrapper {
/// or [`-[SKPayment restoreCompletedTransactionsWithApplicationUsername:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1505992-restorecompletedtransactionswith?language=objc)
/// depending on whether the `applicationUserName` is set.
Future<void> restoreTransactions({String? applicationUserName}) async {
await _hostApi.restoreTransactions(applicationUserName);
await hostApi.restoreTransactions(applicationUserName);
}

/// Present Code Redemption Sheet
Expand All @@ -201,7 +194,7 @@ class SKPaymentQueueWrapper {
/// This method triggers [`-[SKPayment
/// presentCodeRedemptionSheet]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3566726-presentcoderedemptionsheet?language=objc)
Future<void> presentCodeRedemptionSheet() async {
await _hostApi.presentCodeRedemptionSheet();
await hostApi.presentCodeRedemptionSheet();
}

/// Shows the price consent sheet if the user has not yet responded to a
Expand All @@ -213,7 +206,7 @@ class SKPaymentQueueWrapper {
///
/// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc).
Future<void> showPriceConsentIfNeeded() async {
await _hostApi.showPriceConsentIfNeeded();
await hostApi.showPriceConsentIfNeeded();
}

/// Triage a method channel call from the platform and triggers the correct observer method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

import 'dart:async';

import '../messages.g.dart';

InAppPurchaseAPI _hostApi = InAppPurchaseAPI();
import '../in_app_purchase_apis.dart';

// ignore: avoid_classes_with_only_static_members
/// This class contains static methods to manage StoreKit receipts.
Expand All @@ -19,6 +17,6 @@ class SKReceiptManager {
/// For more details on how to validate the receipt data, 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).
/// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt.
static Future<String> retrieveReceiptData() async {
return (await _hostApi.retrieveReceiptData()) ?? '';
return (await hostApi.retrieveReceiptData()) ?? '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import 'dart:async';

import 'package:flutter/services.dart';

import '../in_app_purchase_apis.dart';
import '../messages.g.dart';
import 'sk_product_wrapper.dart';

InAppPurchaseAPI _hostApi = InAppPurchaseAPI();

/// A request maker that handles all the requests made by SKRequest subclasses.
///
/// There are multiple [SKRequest](https://developer.apple.com/documentation/storekit/skrequest?language=objc) subclasses handling different requests in the `StoreKit` with multiple delegate methods,
Expand All @@ -29,7 +28,7 @@ class SKRequestMaker {
Future<SkProductResponseWrapper> startProductRequest(
List<String> productIdentifiers,
) async {
final SKProductsResponseMessage productResponsePigeon = await _hostApi
final SKProductsResponseMessage productResponsePigeon = await hostApi
.startProductRequest(productIdentifiers);

// should products be null or <String>[] ?
Expand All @@ -55,11 +54,11 @@ class SKRequestMaker {
Future<void> startRefreshReceiptRequest({
Map<String, Object?>? receiptProperties,
}) {
return _hostApi.refreshReceipt(receiptProperties: receiptProperties);
return hostApi.refreshReceipt(receiptProperties: receiptProperties);
}

/// Check if current device supports StoreKit 2.
static Future<bool> supportsStoreKit2() async {
return _hostApi.supportsStoreKit2();
return hostApi.supportsStoreKit2();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
dartTestOut: 'test/test_api.g.dart',
objcHeaderOut:
'darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit_objc/include/in_app_purchase_storekit_objc/messages.g.h',
objcSourceOut:
Expand Down Expand Up @@ -238,7 +237,7 @@ class SKProductSubscriptionPeriodMessage {

enum SKSubscriptionPeriodUnitMessage { day, week, month, year }

@HostApi(dartHostTestHandler: 'TestInAppPurchaseApi')
@HostApi()
abstract class InAppPurchaseAPI {
/// Returns if the current device is able to make payments
bool canMakePayments();
Expand Down
Loading