Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[in_app_purchase] Add support for promotional offers through Store-Kit wrappers #4458

Merged
merged 12 commits into from
Nov 15, 2021
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
Expand Up @@ -111,7 +111,7 @@ - (void)testAddPaymentFailure {
XCTAssertEqual(transactionForUpdateBlock.transactionState, SKPaymentTransactionStateFailed);
}

- (void)testAddPaymentSuccessWithMockQueue {
- (void)testAddPaymentSuccessWithoutPaymentDiscount {
XCTestExpectation* expectation =
[self expectationWithDescription:@"result should return success state"];
FlutterMethodCall* call =
Expand All @@ -129,6 +129,9 @@ - (void)testAddPaymentSuccessWithMockQueue {
SKPaymentTransaction* transaction = transactions[0];
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
transactionForUpdateBlock = transaction;
if (@available(iOS 12.2, *)) {
XCTAssertNil(transaction.payment.paymentDiscount);
}
[expectation fulfill];
}
}
Expand All @@ -147,6 +150,93 @@ - (void)testAddPaymentSuccessWithMockQueue {
XCTAssertEqual(transactionForUpdateBlock.transactionState, SKPaymentTransactionStatePurchased);
}

- (void)testAddPaymentSuccessWithPaymentDiscount {
XCTestExpectation* expectation =
[self expectationWithDescription:@"result should return success state"];
FlutterMethodCall* call =
[FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]"
arguments:@{
@"productIdentifier" : @"123",
@"quantity" : @(1),
@"simulatesAskToBuyInSandbox" : @YES,
@"paymentDiscount" : @{
@"identifier" : @"test_identifier",
@"keyIdentifier" : @"test_key_identifier",
@"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003",
@"signature" : @"test_signature",
@"timestamp" : @(1635847102),
}
}];
SKPaymentQueueStub* queue = [SKPaymentQueueStub new];
queue.testState = SKPaymentTransactionStatePurchased;
__block SKPaymentTransaction* transactionForUpdateBlock;
self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction*>* _Nonnull transactions) {
SKPaymentTransaction* transaction = transactions[0];
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
transactionForUpdateBlock = transaction;
if (@available(iOS 12.2, *)) {
SKPaymentDiscount* paymentDiscount = transaction.payment.paymentDiscount;
XCTAssertEqual(paymentDiscount.identifier, @"test_identifier");
XCTAssertEqual(paymentDiscount.keyIdentifier, @"test_key_identifier");
XCTAssertEqualObjects(
paymentDiscount.nonce,
[[NSUUID alloc] initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]);
XCTAssertEqual(paymentDiscount.signature, @"test_signature");
XCTAssertEqual(paymentDiscount.timestamp, @(1635847102));
}
[expectation fulfill];
}
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment* _Nonnull payment, SKProduct* _Nonnull product) {
return YES;
}
updatedDownloads:nil];
[queue addTransactionObserver:self.plugin.paymentQueueHandler];
[self.plugin handleMethodCall:call
result:^(id r){
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(transactionForUpdateBlock.transactionState, SKPaymentTransactionStatePurchased);
}

- (void)testAddPaymentFailureWithInvalidPaymentDiscount {
XCTestExpectation* expectation =
[self expectationWithDescription:@"result should return success state"];
FlutterMethodCall* call =
[FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]"
arguments:@{
@"productIdentifier" : @"123",
@"quantity" : @(1),
@"simulatesAskToBuyInSandbox" : @YES,
@"paymentDiscount" : @{
@"keyIdentifier" : @"test_key_identifier",
@"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003",
@"signature" : @"test_signature",
@"timestamp" : @(1635847102),
}
}];

[self.plugin
handleMethodCall:call
result:^(id r) {
XCTAssertTrue([r isKindOfClass:FlutterError.class]);
FlutterError* result = r;
XCTAssertEqualObjects(result.code, @"storekit_invalid_payment_discount_object");
XCTAssertEqualObjects(result.message,
@"You have requested a payment and specified a payment "
@"discount with invalid properties. When specifying a "
@"payment discount the 'identifier' field is mandatory.");
XCTAssertEqualObjects(result.details, call.arguments);
[expectation fulfill];
}];

[self waitForExpectations:@[ expectation ] timeout:5];
}

- (void)testAddPaymentWithNullSandboxArgument {
XCTestExpectation* expectation =
[self expectationWithDescription:@"result should return success state"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ - (instancetype)initWithMap:(NSDictionary *)map {
[self setValue:map[@"subscriptionGroupIdentifier"] ?: [NSNull null]
forKey:@"subscriptionGroupIdentifier"];
}
if (@available(iOS 12.2, *)) {
NSMutableArray *discounts = [[NSMutableArray alloc] init];
for (NSDictionary *discountMap in map[@"discounts"]) {
[discounts addObject:[[SKProductDiscountStub alloc] initWithMap:discountMap]];
}

[self setValue:discounts forKey:@"discounts"];
}
}
return self;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ @interface TranslatorTest : XCTestCase
@property(strong, nonatomic) NSMutableDictionary *productMap;
@property(strong, nonatomic) NSDictionary *productResponseMap;
@property(strong, nonatomic) NSDictionary *paymentMap;
@property(copy, nonatomic) NSDictionary *paymentDiscountMap;
@property(strong, nonatomic) NSDictionary *transactionMap;
@property(strong, nonatomic) NSDictionary *errorMap;
@property(strong, nonatomic) NSDictionary *localeMap;
Expand Down Expand Up @@ -45,6 +46,9 @@ - (void)setUp {
self.productMap[@"subscriptionPeriod"] = self.periodMap;
self.productMap[@"introductoryPrice"] = self.discountMap;
}
if (@available(iOS 12.2, *)) {
self.productMap[@"discounts"] = @[ self.discountMap ];
}

if (@available(iOS 12.0, *)) {
self.productMap[@"subscriptionGroupIdentifier"] = @"com.group";
Expand All @@ -59,6 +63,13 @@ - (void)setUp {
@"applicationUsername" : @"app user name",
@"simulatesAskToBuyInSandbox" : @(NO)
};
self.paymentDiscountMap = @{
@"identifier" : @"payment_discount_identifier",
@"keyIdentifier" : @"payment_discount_key_identifier",
@"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52",
@"signature" : @"this is a encrypted signature",
@"timestamp" : @([NSDate date].timeIntervalSince1970),
};
NSDictionary *originalTransactionMap = @{
@"transactionIdentifier" : @"567",
@"transactionState" : @(SKPaymentTransactionStatePurchasing),
Expand Down Expand Up @@ -175,4 +186,134 @@ - (void)testSKStorefrontAndSKPaymentTransactionToMap {
}
}

- (void)testSKPaymentDiscountFromMap {
if (@available(iOS 12.2, *)) {
NSString *error = nil;
SKPaymentDiscount *paymentDiscount =
[FIAObjectTranslator getSKPaymentDiscountFromMap:self.paymentDiscountMap withError:&error];

XCTAssertEqual(paymentDiscount.identifier, self.paymentDiscountMap[@"identifier"]);
XCTAssertEqual(paymentDiscount.keyIdentifier, self.paymentDiscountMap[@"keyIdentifier"]);
XCTAssertEqualObjects(paymentDiscount.nonce,
[[NSUUID alloc] initWithUUIDString:self.paymentDiscountMap[@"nonce"]]);
XCTAssertEqual(paymentDiscount.signature, self.paymentDiscountMap[@"signature"]);
XCTAssertEqual(paymentDiscount.timestamp, self.paymentDiscountMap[@"timestamp"]);
}
}

- (void)testSKPaymentDiscountFromMapMissingIdentifier {
if (@available(iOS 12.2, *)) {
NSArray *invalidValues = @[ [NSNull null], @(1), @"" ];

for (id value in invalidValues) {
NSDictionary *discountMap = @{
@"identifier" : value,
@"keyIdentifier" : @"payment_discount_key_identifier",
@"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52",
@"signature" : @"this is a encrypted signature",
@"timestamp" : @([NSDate date].timeIntervalSince1970),
};

NSString *error = nil;
[FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error];

XCTAssertNotNil(error);
XCTAssertEqualObjects(
error, @"When specifying a payment discount the 'identifier' field is mandatory.");
}
}
}

- (void)testSKPaymentDiscountFromMapMissingKeyIdentifier {
if (@available(iOS 12.2, *)) {
NSArray *invalidValues = @[ [NSNull null], @(1), @"" ];

for (id value in invalidValues) {
NSDictionary *discountMap = @{
@"identifier" : @"payment_discount_identifier",
@"keyIdentifier" : value,
@"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52",
@"signature" : @"this is a encrypted signature",
@"timestamp" : @([NSDate date].timeIntervalSince1970),
};

NSString *error = nil;
[FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error];

XCTAssertNotNil(error);
XCTAssertEqualObjects(
error, @"When specifying a payment discount the 'keyIdentifier' field is mandatory.");
}
}
}

- (void)testSKPaymentDiscountFromMapMissingNonce {
if (@available(iOS 12.2, *)) {
NSArray *invalidValues = @[ [NSNull null], @(1), @"" ];

for (id value in invalidValues) {
NSDictionary *discountMap = @{
@"identifier" : @"payment_discount_identifier",
@"keyIdentifier" : @"payment_discount_key_identifier",
@"nonce" : value,
@"signature" : @"this is a encrypted signature",
@"timestamp" : @([NSDate date].timeIntervalSince1970),
};

NSString *error = nil;
[FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error];

XCTAssertNotNil(error);
XCTAssertEqualObjects(error,
@"When specifying a payment discount the 'nonce' field is mandatory.");
}
}
}

- (void)testSKPaymentDiscountFromMapMissingSignature {
if (@available(iOS 12.2, *)) {
NSArray *invalidValues = @[ [NSNull null], @(1), @"" ];

for (id value in invalidValues) {
NSDictionary *discountMap = @{
@"identifier" : @"payment_discount_identifier",
@"keyIdentifier" : @"payment_discount_key_identifier",
@"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52",
@"signature" : value,
@"timestamp" : @([NSDate date].timeIntervalSince1970),
};

NSString *error = nil;
[FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error];

XCTAssertNotNil(error);
XCTAssertEqualObjects(
error, @"When specifying a payment discount the 'signature' field is mandatory.");
}
}
}

- (void)testSKPaymentDiscountFromMapMissingTimestamp {
if (@available(iOS 12.2, *)) {
NSArray *invalidValues = @[ [NSNull null], @"", @(-1) ];

for (id value in invalidValues) {
NSDictionary *discountMap = @{
@"identifier" : @"payment_discount_identifier",
@"keyIdentifier" : @"payment_discount_key_identifier",
@"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52",
@"signature" : @"this is a encrypted signature",
@"timestamp" : value,
};

NSString *error = nil;
[FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error];

XCTAssertNotNil(error);
XCTAssertEqualObjects(
error, @"When specifying a payment discount the 'timestamp' field is mandatory.");
}
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount
API_AVAILABLE(ios(11.2));

// Converts an array of SKProductDiscount instances into an array of dictionaries.
+ (nonnull NSArray *)getMapArrayFromSKProductDiscounts:
(nonnull NSArray<SKProductDiscount *> *)productDiscounts API_AVAILABLE(ios(12.2));

// Converts an instance of SKProductsResponse into a dictionary.
+ (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse;

Expand Down Expand Up @@ -47,6 +51,11 @@ NS_ASSUME_NONNULL_BEGIN
andSKPaymentTransaction:(SKPaymentTransaction *)transaction
API_AVAILABLE(ios(13), macos(10.15), watchos(6.2));

// Creates an instance of the SKPaymentDiscount class based on the supplied dictionary.
+ (nullable SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map
withError:(NSString *_Nullable *_Nullable)error
API_AVAILABLE(ios(12.2));

@end
;

Expand Down
Loading