[in_app_purchase] Convert refreshReceipt(), startObservingPaymentQueue(), stopObservingPaymentQueue(), registerPaymentQueueDelegate(), removePaymentQueueDelegate(), showPriceConsentIfNeeded() to Pigeon (#6165)

Part 3 of https://github.com/flutter/flutter/issues/117910
This commit is contained in:
LouiseHsu
2024-02-21 15:11:05 -08:00
committed by GitHub
parent 784190cd59
commit 1c11001a30
19 changed files with 722 additions and 304 deletions

View File

@ -1,3 +1,8 @@
## 0.3.12
* Converts `refreshReceipt()`, `startObservingPaymentQueue()`, `stopObservingPaymentQueue()`,
`registerPaymentQueueDelegate()`, `removePaymentQueueDelegate()`, `showPriceConsentIfNeeded()` to pigeon.
## 0.3.11 ## 0.3.11
* Fixes SKError.userInfo not being nullable. * Fixes SKError.userInfo not being nullable.

View File

@ -87,30 +87,6 @@
return self; return self;
} }
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) {
[self retrieveReceiptData:call result:result];
} else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) {
[self refreshReceipt:call result:result];
} else if ([@"-[SKPaymentQueue startObservingTransactionQueue]" isEqualToString:call.method]) {
[self startObservingPaymentQueue:result];
} else if ([@"-[SKPaymentQueue stopObservingTransactionQueue]" isEqualToString:call.method]) {
[self stopObservingPaymentQueue:result];
#if TARGET_OS_IOS
} else if ([@"-[SKPaymentQueue registerDelegate]" isEqualToString:call.method]) {
[self registerPaymentQueueDelegate:result];
#endif
} else if ([@"-[SKPaymentQueue removeDelegate]" isEqualToString:call.method]) {
[self removePaymentQueueDelegate:result];
#if TARGET_OS_IOS
} else if ([@"-[SKPaymentQueue showPriceConsentIfNeeded]" isEqualToString:call.method]) {
[self showPriceConsentIfNeeded:result];
#endif
} else {
result(FlutterMethodNotImplemented);
}
}
- (nullable NSNumber *)canMakePaymentsWithError: - (nullable NSNumber *)canMakePaymentsWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error { (FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return @([SKPaymentQueue canMakePayments]); return @([SKPaymentQueue canMakePayments]);
@ -270,62 +246,61 @@
#endif #endif
} }
- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result { - (nullable NSString *)retrieveReceiptDataWithError:
FlutterError *error = nil; (FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&error]; FlutterError *flutterError;
if (error) { NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&flutterError];
result(error); if (flutterError) {
return; *error = flutterError;
return nil;
} }
result(receiptData); return receiptData;
} }
- (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result { - (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties
NSDictionary *arguments = call.arguments; completion:(nonnull void (^)(FlutterError *_Nullable))completion {
SKReceiptRefreshRequest *request; SKReceiptRefreshRequest *request;
if (arguments) { if (receiptProperties) {
if (![arguments isKindOfClass:[NSDictionary class]]) { // if recieptProperties is not null, this call is for testing.
result([FlutterError errorWithCode:@"storekit_invalid_argument"
message:@"Argument type of startRequest is not array"
details:call.arguments]);
return;
}
NSMutableDictionary *properties = [NSMutableDictionary new]; NSMutableDictionary *properties = [NSMutableDictionary new];
properties[SKReceiptPropertyIsExpired] = arguments[@"isExpired"]; properties[SKReceiptPropertyIsExpired] = receiptProperties[@"isExpired"];
properties[SKReceiptPropertyIsRevoked] = arguments[@"isRevoked"]; properties[SKReceiptPropertyIsRevoked] = receiptProperties[@"isRevoked"];
properties[SKReceiptPropertyIsVolumePurchase] = arguments[@"isVolumePurchase"]; properties[SKReceiptPropertyIsVolumePurchase] = receiptProperties[@"isVolumePurchase"];
request = [self getRefreshReceiptRequest:properties]; request = [self getRefreshReceiptRequest:properties];
} else { } else {
request = [self getRefreshReceiptRequest:nil]; request = [self getRefreshReceiptRequest:nil];
} }
FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request];
[self.requestHandlers addObject:handler]; [self.requestHandlers addObject:handler];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response,
NSError *_Nullable error) { NSError *_Nullable error) {
FlutterError *requestError;
if (error) { if (error) {
result([FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" requestError = [FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error"
message:error.localizedDescription message:error.localizedDescription
details:error.description]); details:error.description];
return; completion(requestError);
} }
result(nil); completion(nil);
[weakSelf.requestHandlers removeObject:handler]; [weakSelf.requestHandlers removeObject:handler];
}]; }];
} }
- (void)startObservingPaymentQueue:(FlutterResult)result { - (void)startObservingPaymentQueueWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[_paymentQueueHandler startObservingPaymentQueue]; [_paymentQueueHandler startObservingPaymentQueue];
result(nil);
} }
- (void)stopObservingPaymentQueue:(FlutterResult)result { - (void)stopObservingPaymentQueueWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[_paymentQueueHandler stopObservingPaymentQueue]; [_paymentQueueHandler stopObservingPaymentQueue];
result(nil);
} }
- (void)registerPaymentQueueDelegateWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
#if TARGET_OS_IOS #if TARGET_OS_IOS
- (void)registerPaymentQueueDelegate:(FlutterResult)result {
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
_paymentQueueDelegateCallbackChannel = [FlutterMethodChannel _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel
methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate" methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate"
@ -335,27 +310,25 @@
initWithMethodChannel:_paymentQueueDelegateCallbackChannel]; initWithMethodChannel:_paymentQueueDelegateCallbackChannel];
_paymentQueueHandler.delegate = _paymentQueueDelegate; _paymentQueueHandler.delegate = _paymentQueueDelegate;
} }
result(nil);
}
#endif #endif
}
- (void)removePaymentQueueDelegate:(FlutterResult)result { - (void)removePaymentQueueDelegateWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
_paymentQueueHandler.delegate = nil; _paymentQueueHandler.delegate = nil;
} }
_paymentQueueDelegate = nil; _paymentQueueDelegate = nil;
_paymentQueueDelegateCallbackChannel = nil; _paymentQueueDelegateCallbackChannel = nil;
result(nil);
} }
- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
#if TARGET_OS_IOS #if TARGET_OS_IOS
- (void)showPriceConsentIfNeeded:(FlutterResult)result {
if (@available(iOS 13.4, *)) { if (@available(iOS 13.4, *)) {
[_paymentQueueHandler showPriceConsentIfNeeded]; [_paymentQueueHandler showPriceConsentIfNeeded];
} }
result(nil);
}
#endif #endif
}
- (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { - (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key {
id value = dictionary[key]; id value = dictionary[key];

View File

@ -1,7 +1,6 @@
// Copyright 2013 The Flutter Authors. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Autogenerated from Pigeon (v16.0.4), do not edit directly. // Autogenerated from Pigeon (v16.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon // See also: https://pub.dev/packages/pigeon
@ -271,6 +270,14 @@ NSObject<FlutterMessageCodec> *InAppPurchaseAPIGetCodec(void);
- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName - (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName
error:(FlutterError *_Nullable *_Nonnull)error; error:(FlutterError *_Nullable *_Nonnull)error;
- (void)presentCodeRedemptionSheetWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)presentCodeRedemptionSheetWithError:(FlutterError *_Nullable *_Nonnull)error;
- (nullable NSString *)retrieveReceiptDataWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)refreshReceiptReceiptProperties:(nullable NSDictionary<NSString *, id> *)receiptProperties
completion:(void (^)(FlutterError *_Nullable))completion;
- (void)startObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)stopObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)registerPaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)removePaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable *_Nonnull)error;
@end @end
extern void SetUpInAppPurchaseAPI(id<FlutterBinaryMessenger> binaryMessenger, extern void SetUpInAppPurchaseAPI(id<FlutterBinaryMessenger> binaryMessenger,

View File

@ -1,7 +1,6 @@
// Copyright 2013 The Flutter Authors. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Autogenerated from Pigeon (v16.0.4), do not edit directly. // Autogenerated from Pigeon (v16.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon // See also: https://pub.dev/packages/pigeon
@ -752,4 +751,147 @@ void SetUpInAppPurchaseAPI(id<FlutterBinaryMessenger> binaryMessenger,
[channel setMessageHandler:nil]; [channel setMessageHandler:nil];
} }
} }
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:
@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert(
[api respondsToSelector:@selector(retrieveReceiptDataWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to @selector(retrieveReceiptDataWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
NSString *output = [api retrieveReceiptDataWithError:&error];
callback(wrapResult(output, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:
@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(refreshReceiptReceiptProperties:completion:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(refreshReceiptReceiptProperties:completion:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
NSDictionary<NSString *, id> *arg_receiptProperties = GetNullableObjectAtIndex(args, 0);
[api refreshReceiptReceiptProperties:arg_receiptProperties
completion:^(FlutterError *_Nullable error) {
callback(wrapResult(nil, error));
}];
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI."
@"startObservingPaymentQueue"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(startObservingPaymentQueueWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(startObservingPaymentQueueWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
[api startObservingPaymentQueueWithError:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI."
@"stopObservingPaymentQueue"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(stopObservingPaymentQueueWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(stopObservingPaymentQueueWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
[api stopObservingPaymentQueueWithError:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI."
@"registerPaymentQueueDelegate"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(registerPaymentQueueDelegateWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(registerPaymentQueueDelegateWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
[api registerPaymentQueueDelegateWithError:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI."
@"removePaymentQueueDelegate"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(removePaymentQueueDelegateWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(removePaymentQueueDelegateWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
[api removePaymentQueueDelegateWithError:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI."
@"showPriceConsentIfNeeded"
binaryMessenger:binaryMessenger
codec:InAppPurchaseAPIGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(showPriceConsentIfNeededWithError:)],
@"InAppPurchaseAPI api (%@) doesn't respond to "
@"@selector(showPriceConsentIfNeededWithError:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
[api showPriceConsentIfNeededWithError:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
} }

View File

@ -26,7 +26,8 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"

View File

@ -26,20 +26,6 @@
- (void)tearDown { - (void)tearDown {
} }
- (void)testInvalidMethodCall {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect result to be not implemented"];
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"invalid" arguments:NULL];
__block id result;
[self.plugin handleMethodCall:call
result:^(id r) {
[expectation fulfill];
result = r;
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(result, FlutterMethodNotImplemented);
}
- (void)testCanMakePayments { - (void)testCanMakePayments {
FlutterError *error; FlutterError *error;
NSNumber *result = [self.plugin canMakePaymentsWithError:&error]; NSNumber *result = [self.plugin canMakePaymentsWithError:&error];
@ -299,17 +285,8 @@
} }
- (void)testRetrieveReceiptDataSuccess { - (void)testRetrieveReceiptDataSuccess {
XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; FlutterError *error;
FlutterMethodCall *call = [FlutterMethodCall NSString *result = [self.plugin retrieveReceiptDataWithError:&error];
methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]"
arguments:nil];
__block NSDictionary *result;
[self.plugin handleMethodCall:call
result:^(id r) {
result = r;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertNotNil(result); XCTAssertNotNil(result);
XCTAssert([result isKindOfClass:[NSString class]]); XCTAssert([result isKindOfClass:[NSString class]]);
} }
@ -317,71 +294,47 @@
- (void)testRetrieveReceiptDataNil { - (void)testRetrieveReceiptDataNil {
NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]);
OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil);
XCTestExpectation *expectation = [self expectationWithDescription:@"nil receipt data retrieved"]; FlutterError *error;
FlutterMethodCall *call = [FlutterMethodCall NSString *result = [self.plugin retrieveReceiptDataWithError:&error];
methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]"
arguments:nil];
__block NSDictionary *result;
[self.plugin handleMethodCall:call
result:^(id r) {
result = r;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertNil(result); XCTAssertNil(result);
} }
- (void)testRetrieveReceiptDataError { - (void)testRetrieveReceiptDataError {
XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"];
FlutterMethodCall *call = [FlutterMethodCall
methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]"
arguments:nil];
__block NSDictionary *result;
self.receiptManagerStub.returnError = YES; self.receiptManagerStub.returnError = YES;
[self.plugin handleMethodCall:call
result:^(id r) { FlutterError *error;
result = r; NSString *result = [self.plugin retrieveReceiptDataWithError:&error];
[expectation fulfill];
}]; XCTAssertNil(result);
[self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(error);
XCTAssertNotNil(result); NSDictionary *details = error.details;
XCTAssert([result isKindOfClass:[FlutterError class]]);
NSDictionary *details = ((FlutterError *)result).details;
XCTAssertNotNil(details[@"error"]); XCTAssertNotNil(details[@"error"]);
NSNumber *errorCode = (NSNumber *)details[@"error"][@"code"]; NSNumber *errorCode = (NSNumber *)details[@"error"][@"code"];
XCTAssertEqual(errorCode, [NSNumber numberWithInteger:99]); XCTAssertEqual(errorCode, [NSNumber numberWithInteger:99]);
} }
- (void)testRefreshReceiptRequest { - (void)testRefreshReceiptRequest {
XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; XCTestExpectation *expectation =
FlutterMethodCall *call = [self expectationWithDescription:@"completion handler successfully called"];
[FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin refreshReceipt:result:]" [self.plugin refreshReceiptReceiptProperties:nil
arguments:nil]; completion:^(FlutterError *_Nullable error) {
__block BOOL result = NO; [expectation fulfill];
[self.plugin handleMethodCall:call }];
result:^(id r) {
result = YES;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5]; [self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertTrue(result);
} }
/// presentCodeRedemptionSheetWithError:error is only available on iOS
#if TARGET_OS_IOS
- (void)testPresentCodeRedemptionSheet { - (void)testPresentCodeRedemptionSheet {
XCTestExpectation *expectation = FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
[self expectationWithDescription:@"expect successfully present Code Redemption Sheet"]; self.plugin.paymentQueueHandler = mockHandler;
FlutterMethodCall *call = [FlutterMethodCall
methodCallWithMethodName:@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" FlutterError *error;
arguments:nil]; [self.plugin presentCodeRedemptionSheetWithError:&error];
__block BOOL callbackInvoked = NO;
[self.plugin handleMethodCall:call OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]);
result:^(id r) {
callbackInvoked = YES;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertTrue(callbackInvoked);
} }
#endif
- (void)testGetPendingTransactions { - (void)testGetPendingTransactions {
SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class);
@ -420,48 +373,28 @@
} }
- (void)testStartObservingPaymentQueue { - (void)testStartObservingPaymentQueue {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Should return success result"];
FlutterMethodCall *startCall = [FlutterMethodCall
methodCallWithMethodName:@"-[SKPaymentQueue startObservingTransactionQueue]"
arguments:nil];
FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
self.plugin.paymentQueueHandler = mockHandler; self.plugin.paymentQueueHandler = mockHandler;
[self.plugin handleMethodCall:startCall
result:^(id _Nullable result) {
XCTAssertNil(result);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5]; FlutterError *error;
[self.plugin startObservingPaymentQueueWithError:&error];
OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); OCMVerify(times(1), [mockHandler startObservingPaymentQueue]);
} }
- (void)testStopObservingPaymentQueue { - (void)testStopObservingPaymentQueue {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Should return success result"];
FlutterMethodCall *stopCall =
[FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue stopObservingTransactionQueue]"
arguments:nil];
FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
self.plugin.paymentQueueHandler = mockHandler; self.plugin.paymentQueueHandler = mockHandler;
[self.plugin handleMethodCall:stopCall
result:^(id _Nullable result) {
XCTAssertNil(result);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5]; FlutterError *error;
[self.plugin stopObservingPaymentQueueWithError:&error];
OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]);
} }
#if TARGET_OS_IOS #if TARGET_OS_IOS
- (void)testRegisterPaymentQueueDelegate { - (void)testRegisterPaymentQueueDelegate {
if (@available(iOS 13, *)) { if (@available(iOS 13, *)) {
FlutterMethodCall *call =
[FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue registerDelegate]"
arguments:nil];
self.plugin.paymentQueueHandler = self.plugin.paymentQueueHandler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
transactionsUpdated:nil transactionsUpdated:nil
@ -475,9 +408,8 @@
// Verify the delegate is nil before we register one. // Verify the delegate is nil before we register one.
XCTAssertNil(self.plugin.paymentQueueHandler.delegate); XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
[self.plugin handleMethodCall:call FlutterError *error;
result:^(id r){ [self.plugin registerPaymentQueueDelegateWithError:&error];
}];
// Verify the delegate is not nil after we registered one. // Verify the delegate is not nil after we registered one.
XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate);
@ -487,10 +419,6 @@
- (void)testRemovePaymentQueueDelegate { - (void)testRemovePaymentQueueDelegate {
if (@available(iOS 13, *)) { if (@available(iOS 13, *)) {
FlutterMethodCall *call =
[FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue removeDelegate]"
arguments:nil];
self.plugin.paymentQueueHandler = self.plugin.paymentQueueHandler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
transactionsUpdated:nil transactionsUpdated:nil
@ -505,9 +433,8 @@
// Verify the delegate is not nil before removing it. // Verify the delegate is not nil before removing it.
XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate);
[self.plugin handleMethodCall:call FlutterError *error;
result:^(id r){ [self.plugin removePaymentQueueDelegateWithError:&error];
}];
// Verify the delegate is nill after removing it. // Verify the delegate is nill after removing it.
XCTAssertNil(self.plugin.paymentQueueHandler.delegate); XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
@ -516,16 +443,11 @@
#if TARGET_OS_IOS #if TARGET_OS_IOS
- (void)testShowPriceConsentIfNeeded { - (void)testShowPriceConsentIfNeeded {
FlutterMethodCall *call =
[FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue showPriceConsentIfNeeded]"
arguments:nil];
FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class);
self.plugin.paymentQueueHandler = mockQueueHandler; self.plugin.paymentQueueHandler = mockQueueHandler;
[self.plugin handleMethodCall:call FlutterError *error;
result:^(id r){ [self.plugin showPriceConsentIfNeededWithError:&error];
}];
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability" #pragma clang diagnostic ignored "-Wpartial-availability"

View File

@ -1,3 +1,6 @@
// 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.
// Autogenerated from Pigeon (v16.0.4), do not edit directly. // Autogenerated from Pigeon (v16.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon // See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
@ -788,4 +791,173 @@ class InAppPurchaseAPI {
return; return;
} }
} }
Future<String?> retrieveReceiptData() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as String?);
}
}
Future<void> refreshReceipt(
{Map<String?, Object?>? receiptProperties}) async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList = await __pigeon_channel
.send(<Object?>[receiptProperties]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<void> startObservingPaymentQueue() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<void> stopObservingPaymentQueue() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<void> registerPaymentQueueDelegate() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<void> removePaymentQueueDelegate() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<void> showPriceConsentIfNeeded() async {
const String __pigeon_channelName =
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded';
final BasicMessageChannel<Object?> __pigeon_channel =
BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(null) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
} }

View File

@ -86,16 +86,16 @@ class SKPaymentQueueWrapper {
/// ///
/// Call this method when the first listener is subscribed to the /// Call this method when the first listener is subscribed to the
/// [InAppPurchaseStoreKitPlatform.purchaseStream]. /// [InAppPurchaseStoreKitPlatform.purchaseStream].
Future<void> startObservingTransactionQueue() => channel Future<void> startObservingTransactionQueue() =>
.invokeMethod<void>('-[SKPaymentQueue startObservingTransactionQueue]'); _hostApi.startObservingPaymentQueue();
/// Instructs the iOS implementation to remove the transaction observer and /// Instructs the iOS implementation to remove the transaction observer and
/// stop listening to it. /// stop listening to it.
/// ///
/// Call this when there are no longer any listeners subscribed to the /// Call this when there are no longer any listeners subscribed to the
/// [InAppPurchaseStoreKitPlatform.purchaseStream]. /// [InAppPurchaseStoreKitPlatform.purchaseStream].
Future<void> stopObservingTransactionQueue() => channel Future<void> stopObservingTransactionQueue() =>
.invokeMethod<void>('-[SKPaymentQueue stopObservingTransactionQueue]'); _hostApi.stopObservingPaymentQueue();
/// Sets an implementation of the [SKPaymentQueueDelegateWrapper]. /// Sets an implementation of the [SKPaymentQueueDelegateWrapper].
/// ///
@ -109,10 +109,10 @@ class SKPaymentQueueWrapper {
/// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)). /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)).
Future<void> setDelegate(SKPaymentQueueDelegateWrapper? delegate) async { Future<void> setDelegate(SKPaymentQueueDelegateWrapper? delegate) async {
if (delegate == null) { if (delegate == null) {
await channel.invokeMethod<void>('-[SKPaymentQueue removeDelegate]'); await _hostApi.removePaymentQueueDelegate();
paymentQueueDelegateChannel.setMethodCallHandler(null); paymentQueueDelegateChannel.setMethodCallHandler(null);
} else { } else {
await channel.invokeMethod<void>('-[SKPaymentQueue registerDelegate]'); await _hostApi.registerPaymentQueueDelegate();
paymentQueueDelegateChannel paymentQueueDelegateChannel
.setMethodCallHandler(handlePaymentQueueDelegateCallbacks); .setMethodCallHandler(handlePaymentQueueDelegateCallbacks);
} }
@ -207,8 +207,7 @@ class SKPaymentQueueWrapper {
/// ///
/// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc). /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc).
Future<void> showPriceConsentIfNeeded() async { Future<void> showPriceConsentIfNeeded() async {
await channel await _hostApi.showPriceConsentIfNeeded();
.invokeMethod<void>('-[SKPaymentQueue showPriceConsentIfNeeded]');
} }
/// Triage a method channel call from the platform and triggers the correct observer method. /// Triage a method channel call from the platform and triggers the correct observer method.
@ -354,7 +353,7 @@ class SKError {
/// ///
/// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). /// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc).
@JsonKey(defaultValue: <String, dynamic>{}) @JsonKey(defaultValue: <String, dynamic>{})
final Map<String?, Object?> userInfo; final Map<String?, Object?>? userInfo;
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {

View File

@ -4,7 +4,9 @@
import 'dart:async'; import 'dart:async';
import '../channel.dart'; import '../messages.g.dart';
InAppPurchaseAPI _hostApi = InAppPurchaseAPI();
// ignore: avoid_classes_with_only_static_members // ignore: avoid_classes_with_only_static_members
/// This class contains static methods to manage StoreKit receipts. /// This class contains static methods to manage StoreKit receipts.
@ -17,8 +19,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). /// 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. /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt.
static Future<String> retrieveReceiptData() async { static Future<String> retrieveReceiptData() async {
return (await channel.invokeMethod<String>( return (await _hostApi.retrieveReceiptData()) ?? '';
'-[InAppPurchasePlugin retrieveReceiptData:result:]')) ??
'';
} }
} }

View File

@ -6,7 +6,6 @@ import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../channel.dart';
import '../messages.g.dart'; import '../messages.g.dart';
import 'sk_product_wrapper.dart'; import 'sk_product_wrapper.dart';
@ -54,9 +53,6 @@ class SKRequestMaker {
/// * isVolumePurchase: whether the receipt is a Volume Purchase Plan receipt. /// * isVolumePurchase: whether the receipt is a Volume Purchase Plan receipt.
Future<void> startRefreshReceiptRequest( Future<void> startRefreshReceiptRequest(
{Map<String, dynamic>? receiptProperties}) { {Map<String, dynamic>? receiptProperties}) {
return channel.invokeMethod<void>( return _hostApi.refreshReceipt(receiptProperties: receiptProperties);
'-[InAppPurchasePlugin refreshReceipt:result:]',
receiptProperties,
);
} }
} }

View File

@ -0,0 +1,3 @@
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.

View File

@ -243,4 +243,19 @@ abstract class InAppPurchaseAPI {
void restoreTransactions(String? applicationUserName); void restoreTransactions(String? applicationUserName);
void presentCodeRedemptionSheet(); void presentCodeRedemptionSheet();
String? retrieveReceiptData();
@async
void refreshReceipt({Map<String, Object?>? receiptProperties});
void startObservingPaymentQueue();
void stopObservingPaymentQueue();
void registerPaymentQueueDelegate();
void removePaymentQueueDelegate();
void showPriceConsentIfNeeded();
} }

View File

@ -2,7 +2,7 @@ name: in_app_purchase_storekit
description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
version: 0.3.11 version: 0.3.12
environment: environment:
sdk: ^3.2.3 sdk: ^3.2.3

View File

@ -5,7 +5,6 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/src/channel.dart';
import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/src/messages.g.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
@ -13,11 +12,6 @@ import '../store_kit_wrappers/sk_test_stub_objects.dart';
import '../test_api.g.dart'; import '../test_api.g.dart';
class FakeStoreKitPlatform implements TestInAppPurchaseApi { class FakeStoreKitPlatform implements TestInAppPurchaseApi {
FakeStoreKitPlatform() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, onMethodCall);
}
// pre-configured store information // pre-configured store information
String? receiptData; String? receiptData;
late Set<String> validProductIDs; late Set<String> validProductIDs;
@ -32,6 +26,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
SKError? testRestoredError; SKError? testRestoredError;
bool queueIsActive = false; bool queueIsActive = false;
Map<String, dynamic> discountReceived = <String, dynamic>{}; Map<String, dynamic> discountReceived = <String, dynamic>{};
bool isPaymentQueueDelegateRegistered = false;
void reset() { void reset() {
transactionList = <SKPaymentTransactionWrapper>[]; transactionList = <SKPaymentTransactionWrapper>[];
@ -57,6 +52,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
testRestoredError = null; testRestoredError = null;
queueIsActive = false; queueIsActive = false;
discountReceived = <String, dynamic>{}; discountReceived = <String, dynamic>{};
isPaymentQueueDelegateRegistered = false;
} }
SKPaymentTransactionWrapper createPendingTransaction(String id, SKPaymentTransactionWrapper createPendingTransaction(String id,
@ -120,25 +116,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
transactionIdentifier: transactionId); transactionIdentifier: transactionId);
} }
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
case '-[InAppPurchasePlugin retrieveReceiptData:result:]':
if (receiptData != null) {
return Future<String>.value(receiptData!);
} else {
throw PlatformException(code: 'no_receipt_data');
}
case '-[InAppPurchasePlugin refreshReceipt:result:]':
receiptData = 'refreshed receipt data';
return Future<void>.sync(() {});
case '-[SKPaymentQueue startObservingTransactionQueue]':
queueIsActive = true;
case '-[SKPaymentQueue stopObservingTransactionQueue]':
queueIsActive = false;
}
return Future<void>.sync(() {});
}
@override @override
bool canMakePayments() { bool canMakePayments() {
return true; return true;
@ -246,4 +223,42 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
return Future<SKProductsResponseMessage>.value( return Future<SKProductsResponseMessage>.value(
SkProductResponseWrapper.convertToPigeon(response)); SkProductResponseWrapper.convertToPigeon(response));
} }
@override
Future<void> refreshReceipt({Map<String?, dynamic>? receiptProperties}) {
receiptData = 'refreshed receipt data';
return Future<void>.sync(() {});
}
@override
void registerPaymentQueueDelegate() {
isPaymentQueueDelegateRegistered = true;
}
@override
void removePaymentQueueDelegate() {
isPaymentQueueDelegateRegistered = false;
}
@override
String retrieveReceiptData() {
if (receiptData != null) {
return receiptData!;
} else {
throw PlatformException(code: 'no_receipt_data');
}
}
@override
void showPriceConsentIfNeeded() {}
@override
void startObservingPaymentQueue() {
queueIsActive = true;
}
@override
void stopObservingPaymentQueue() {
queueIsActive = false;
}
} }

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
@ -17,9 +16,6 @@ void main() {
setUpAll(() { setUpAll(() {
TestInAppPurchaseApi.setup(fakeStoreKitPlatform); TestInAppPurchaseApi.setup(fakeStoreKitPlatform);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
SystemChannels.platform, fakeStoreKitPlatform.onMethodCall);
}); });
group('present code redemption sheet', () { group('present code redemption sheet', () {

View File

@ -23,9 +23,6 @@ void main() {
setUpAll(() { setUpAll(() {
TestInAppPurchaseApi.setup(fakeStoreKitPlatform); TestInAppPurchaseApi.setup(fakeStoreKitPlatform);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
SystemChannels.platform, fakeStoreKitPlatform.onMethodCall);
}); });
setUp(() { setUp(() {

View File

@ -4,7 +4,6 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_storekit/src/channel.dart';
import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/src/messages.g.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
import '../test_api.g.dart'; import '../test_api.g.dart';
@ -17,9 +16,6 @@ void main() {
setUpAll(() { setUpAll(() {
TestInAppPurchaseApi.setup(fakeStoreKitPlatform); TestInAppPurchaseApi.setup(fakeStoreKitPlatform);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
SystemChannels.platform, fakeStoreKitPlatform.onMethodCall);
}); });
setUp(() {}); setUp(() {});
@ -76,10 +72,10 @@ void main() {
}); });
test('refreshed receipt', () async { test('refreshed receipt', () async {
final int receiptCountBefore = fakeStoreKitPlatform.refreshReceipt; final int receiptCountBefore = fakeStoreKitPlatform.refreshReceiptCount;
await SKRequestMaker().startRefreshReceiptRequest( await SKRequestMaker().startRefreshReceiptRequest(
receiptProperties: <String, dynamic>{'isExpired': true}); receiptProperties: <String, dynamic>{'isExpired': true});
expect(fakeStoreKitPlatform.refreshReceipt, receiptCountBefore + 1); expect(fakeStoreKitPlatform.refreshReceiptCount, receiptCountBefore + 1);
expect(fakeStoreKitPlatform.refreshReceiptParam, expect(fakeStoreKitPlatform.refreshReceiptParam,
<String, dynamic>{'isExpired': true}); <String, dynamic>{'isExpired': true});
}); });
@ -175,9 +171,9 @@ void main() {
}); });
test('showPriceConsentIfNeeded should call methodChannel', () async { test('showPriceConsentIfNeeded should call methodChannel', () async {
expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, false); expect(fakeStoreKitPlatform.showPriceConsent, false);
await SKPaymentQueueWrapper().showPriceConsentIfNeeded(); await SKPaymentQueueWrapper().showPriceConsentIfNeeded();
expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, true); expect(fakeStoreKitPlatform.showPriceConsent, true);
}); });
}); });
@ -192,10 +188,6 @@ void main() {
} }
class FakeStoreKitPlatform implements TestInAppPurchaseApi { class FakeStoreKitPlatform implements TestInAppPurchaseApi {
FakeStoreKitPlatform() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, onMethodCall);
}
// get product request // get product request
List<dynamic> startProductRequestParam = <dynamic>[]; List<dynamic> startProductRequestParam = <dynamic>[];
bool getProductRequestFailTest = false; bool getProductRequestFailTest = false;
@ -205,7 +197,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
bool getReceiptFailTest = false; bool getReceiptFailTest = false;
// refresh receipt request // refresh receipt request
int refreshReceipt = 0; int refreshReceiptCount = 0;
late Map<String, dynamic> refreshReceiptParam; late Map<String, dynamic> refreshReceiptParam;
// payment queue // payment queue
@ -217,7 +209,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
bool presentCodeRedemption = false; bool presentCodeRedemption = false;
// show price consent sheet // show price consent sheet
bool showPriceConsentIfNeeded = false; bool showPriceConsent = false;
// indicate if the payment queue delegate is registered // indicate if the payment queue delegate is registered
bool isPaymentQueueDelegateRegistered = false; bool isPaymentQueueDelegateRegistered = false;
@ -225,39 +217,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
// Listen to purchase updates // Listen to purchase updates
bool? queueIsActive; bool? queueIsActive;
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
// request makers
case '-[InAppPurchasePlugin refreshReceipt:result:]':
refreshReceipt++;
refreshReceiptParam = Map.castFrom<dynamic, dynamic, String, dynamic>(
call.arguments as Map<dynamic, dynamic>);
return Future<void>.sync(() {});
// receipt manager
case '-[InAppPurchasePlugin retrieveReceiptData:result:]':
if (getReceiptFailTest) {
throw Exception('some arbitrary error');
}
return Future<String>.value('receipt data');
case '-[SKPaymentQueue startObservingTransactionQueue]':
queueIsActive = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue stopObservingTransactionQueue]':
queueIsActive = false;
return Future<void>.sync(() {});
case '-[SKPaymentQueue registerDelegate]':
isPaymentQueueDelegateRegistered = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue removeDelegate]':
isPaymentQueueDelegateRegistered = false;
return Future<void>.sync(() {});
case '-[SKPaymentQueue showPriceConsentIfNeeded]':
showPriceConsentIfNeeded = true;
return Future<void>.sync(() {});
}
return Future<dynamic>.error('method not mocked');
}
@override @override
void addPayment(Map<String?, Object?> paymentMap) { void addPayment(Map<String?, Object?> paymentMap) {
payments payments
@ -304,6 +263,47 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi {
} }
return Future<SKProductsResponseMessage>.value(dummyProductResponseMessage); return Future<SKProductsResponseMessage>.value(dummyProductResponseMessage);
} }
@override
void registerPaymentQueueDelegate() {
isPaymentQueueDelegateRegistered = true;
}
@override
void removePaymentQueueDelegate() {
isPaymentQueueDelegateRegistered = false;
}
@override
void startObservingPaymentQueue() {
queueIsActive = true;
}
@override
void stopObservingPaymentQueue() {
queueIsActive = false;
}
@override
String retrieveReceiptData() {
if (getReceiptFailTest) {
throw Exception('some arbitrary error');
}
return 'receipt data';
}
@override
Future<void> refreshReceipt({Map<String?, dynamic>? receiptProperties}) {
refreshReceiptCount++;
refreshReceiptParam =
Map.castFrom<dynamic, dynamic, String, dynamic>(receiptProperties!);
return Future<void>.sync(() {});
}
@override
void showPriceConsentIfNeeded() {
showPriceConsent = true;
}
} }
class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {} class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {}

View File

@ -4,18 +4,18 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_storekit/src/channel.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
import '../fakes/fake_storekit_platform.dart';
import '../test_api.g.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform();
setUpAll(() { setUpAll(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestInAppPurchaseApi.setup(fakeStoreKitPlatform);
.setMockMethodCallHandler(
SystemChannels.platform, fakeStoreKitPlatform.onMethodCall);
}); });
test( test(
@ -146,25 +146,3 @@ class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {
return false; return false;
} }
} }
class FakeStoreKitPlatform {
FakeStoreKitPlatform() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, onMethodCall);
}
// indicate if the payment queue delegate is registered
bool isPaymentQueueDelegateRegistered = false;
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
case '-[SKPaymentQueue registerDelegate]':
isPaymentQueueDelegateRegistered = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue removeDelegate]':
isPaymentQueueDelegateRegistered = false;
return Future<void>.sync(() {});
}
return Future<dynamic>.error('method not mocked');
}
}

View File

@ -1,3 +1,6 @@
// 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.
// Autogenerated from Pigeon (v16.0.4), do not edit directly. // Autogenerated from Pigeon (v16.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon // See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers
@ -102,6 +105,20 @@ abstract class TestInAppPurchaseApi {
void presentCodeRedemptionSheet(); void presentCodeRedemptionSheet();
String? retrieveReceiptData();
Future<void> refreshReceipt({Map<String?, Object?>? receiptProperties});
void startObservingPaymentQueue();
void stopObservingPaymentQueue();
void registerPaymentQueueDelegate();
void removePaymentQueueDelegate();
void showPriceConsentIfNeeded();
static void setup(TestInAppPurchaseApi? api, static void setup(TestInAppPurchaseApi? api,
{BinaryMessenger? binaryMessenger}) { {BinaryMessenger? binaryMessenger}) {
{ {
@ -331,5 +348,185 @@ abstract class TestInAppPurchaseApi {
}); });
} }
} }
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
final String? output = api.retrieveReceiptData();
return <Object?>[output];
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt was null.');
final List<Object?> args = (message as List<Object?>?)!;
final Map<String?, Object?>? arg_receiptProperties =
(args[0] as Map<Object?, Object?>?)?.cast<String?, Object?>();
try {
await api.refreshReceipt(receiptProperties: arg_receiptProperties);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
api.startObservingPaymentQueue();
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
api.stopObservingPaymentQueue();
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
api.registerPaymentQueueDelegate();
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
api.removePaymentQueueDelegate();
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
Object?>(
'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded',
pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(__pigeon_channel,
(Object? message) async {
try {
api.showPriceConsentIfNeeded();
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
} }
} }