Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
@@ -0,0 +1,28 @@
// 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 <StoreKit/StoreKit.h>
#import "FIAPRequestHandler.h"
#import "InAppPurchasePlugin.h"

@interface InAppPurchasePlugin ()

// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after
// the request is finished.
@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers;

// Callback channel to dart used for when a function from the transaction observer is triggered.
@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel;

// Transaction observer methods
- (void)handleTransactionsUpdated:(NSArray<SKPaymentTransaction *> *)transactions;
- (void)handleTransactionsRemoved:(NSArray<SKPaymentTransaction *> *)transactions;
- (void)handleTransactionRestoreFailed:(NSError *)error;
- (void)restoreCompletedTransactionsFinished;
- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product;

// Dependency Injection
- (FIAPRequestHandler *)getHandler:(SKRequest *)request;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating a method that will be overwritten with a partial mock I think you should pass in a factory to the constructor InAppPurchasePlugin.

InAppPurchasePlugin *plugin =
  [[InAppPurchasePlugin alloc]
    initWithReceiptManager:nil 
    handlerFactory:^(){ return [[FIAPRequestHandler alloc] initWithRequest:request]; }];

You were concerned about fiddling with the init method since it has users. You can get around that by adding this new initializer to InAppPurchasePlugin+TestOnly.h and calling it from the existing initWithRequest: initializer.

Partial mocks are something we should try hard to avoid. We've had to use them in the past because of oddities that disallow us to test without crashing. That isn't the case here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm misunderstanding something sorry - so for example

FIAPRequestHandler *handler = [self getHandler:request];
[self.requestHandlers addObject:handler];

What will these lines then look like? How do I reference the handlerFactory and ask it to please spit out another handler?
Additionally, the request param is only available within startProductRequestProductIdentifiers: , so how would I be able to call it inside the initWithRequest: initializer?

@gaaclarke gaaclarke Mar 1, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would look like this:

  FIAPRequestHandler *handler = self.handlerFactory();
  [self.requestHandlers addObject:handler];

You don't want to call the block in your init method. You want to hold on to it so that whenever you want a handler you can call it.

This is going to help: http://fuckingblocksyntax.com/ ;)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doneeee


@end
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,14 @@
#import "FIAPReceiptManager.h"
#import "FIAPRequestHandler.h"
#import "FIAPaymentQueueHandler.h"
#import "InAppPurchasePlugin+TestOnly.h"

@interface InAppPurchasePlugin ()

// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after
// the request is finished.
@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers;

// After querying the product, the available products will be saved in the map to be used
// for purchase.
@property(strong, nonatomic, readonly) NSMutableDictionary *productsCache;

// Callback channel to dart used for when a function from the transaction observer is triggered.
@property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel;

// Callback channel to dart used for when a function from the payment queue delegate is triggered.
@property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel;
@property(strong, nonatomic, readonly) NSObject<FlutterPluginRegistrar> *registrar;
Expand Down Expand Up @@ -117,7 +111,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray<NSString *> *)productIden
FlutterError *_Nullable))completion {
SKProductsRequest *request =
[self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]];
FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request];
FIAPRequestHandler *handler = [self getHandler:request];
[self.requestHandlers addObject:handler];
__weak typeof(self) weakSelf = self;

Expand Down Expand Up @@ -282,6 +276,7 @@ - (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperti
message:error.localizedDescription
details:error.description];
completion(requestError);
return;
}
completion(nil);
[weakSelf.requestHandlers removeObject:handler];
Expand Down Expand Up @@ -393,6 +388,10 @@ - (SKProduct *)getProduct:(NSString *)productID {
return [self.productsCache objectForKey:productID];
}

- (FIAPRequestHandler *)getHandler:(SKRequest *)request {
return [[FIAPRequestHandler alloc] initWithRequest:request];
}

- (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties {
return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -606,7 +606,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand Down
Loading