Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCProductInfo: added SKStoreFront.countryCode to post store_country with receipts #2093

Merged
merged 1 commit into from
Nov 30, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 17 additions & 6 deletions Purchases/Public/RCPurchases.m
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ - (void)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper
switch (transaction.transactionState) {
case SKPaymentTransactionStateRestored: // For observer mode
case SKPaymentTransactionStatePurchased: {
[self handlePurchasedTransaction:transaction];
[self handlePurchasedTransaction:transaction countryCode:storeKitWrapper.countryCode];
break;
}
case SKPaymentTransactionStateFailed: {
Expand Down Expand Up @@ -1209,24 +1209,33 @@ - (void) storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper
}];
}

- (void)handlePurchasedTransaction:(SKPaymentTransaction *)transaction {
- (void)handlePurchasedTransaction:(SKPaymentTransaction *)transaction
countryCode:(NSString *)countryCode {
[self receiptData:^(NSData * _Nonnull data) {
if (data.length == 0) {
[self handleReceiptPostWithTransaction:transaction
purchaserInfo:nil
subscriberAttributes:nil
error:RCPurchasesErrorUtils.missingReceiptFileError];
} else {
[self fetchProductsAndPostReceiptWithTransaction:transaction data:data];
[self fetchProductsAndPostReceiptWithTransaction:transaction
countryCode:countryCode
data:data];
}
}];
}

- (void)fetchProductsAndPostReceiptWithTransaction:(SKPaymentTransaction *)transaction data:(NSData *)data {
- (void)fetchProductsAndPostReceiptWithTransaction:(SKPaymentTransaction *)transaction
countryCode:(NSString *)countryCode
data:(NSData *)data {
if ([self productIdentifierFrom:transaction]) {
[self productsWithIdentifiers:@[[self productIdentifierFrom:transaction]]
completionBlock:^(NSArray<SKProduct *> *products) {
[self postReceiptWithTransaction:transaction data:data products:products];
[self postReceiptWithTransaction:transaction
countryCode:countryCode
data:data
products:products
];
}];
} else {
[self handleReceiptPostWithTransaction:transaction
Expand All @@ -1237,15 +1246,17 @@ - (void)fetchProductsAndPostReceiptWithTransaction:(SKPaymentTransaction *)trans
}

- (void)postReceiptWithTransaction:(SKPaymentTransaction *)transaction
countryCode:(NSString *)countryCode
data:(NSData *)data
products:(NSArray<SKProduct *> *)products {
SKProduct *product = products.lastObject;
RCSubscriberAttributeDict subscriberAttributes = self.unsyncedAttributesByKey;
RCProductInfo *productInfo = nil;
NSString *presentedOffering = nil;

if (product) {
RCProductInfoExtractor *productInfoExtractor = [[RCProductInfoExtractor alloc] init];
productInfo = [productInfoExtractor extractInfoFromProduct:product];
productInfo = [productInfoExtractor extractInfoFromProduct:product countryCode:countryCode];

@synchronized (self) {
presentedOffering = self.presentedOfferingsByProductIdentifier[productInfo.productIdentifier];
Expand Down
2 changes: 2 additions & 0 deletions Purchases/Purchasing/RCProductInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ RCPaymentMode RCPaymentModeFromSKProductDiscountPaymentMode(SKProductDiscountPay
@property (nonatomic, readonly, copy) NSString *productIdentifier;
@property (nonatomic, readonly, assign) RCPaymentMode paymentMode;
@property (nonatomic, readonly, copy) NSString *currencyCode;
@property (nonatomic, nullable, readonly, copy) NSString *countryCode;
@property (nonatomic, readonly, copy) NSDecimalNumber *price;
@property (nonatomic, nullable, readonly, copy) NSString *normalDuration;
@property (nonatomic, nullable, readonly, copy) NSString *introDuration;
Expand All @@ -42,6 +43,7 @@ RCPaymentMode RCPaymentModeFromSKProductDiscountPaymentMode(SKProductDiscountPay
- (instancetype)initWithProductIdentifier:(NSString *)productIdentifier
paymentMode:(RCPaymentMode)paymentMode
currencyCode:(NSString *)currencyCode
countryCode:(nullable NSString *)countryCode
price:(NSDecimalNumber *)price
normalDuration:(nullable NSString *)normalDuration
introDuration:(nullable NSString *)introDuration
Expand Down
7 changes: 7 additions & 0 deletions Purchases/Purchasing/RCProductInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ @interface RCProductInfo ()
@property (nonatomic, copy) NSString *productIdentifier;
@property (nonatomic, assign) RCPaymentMode paymentMode;
@property (nonatomic, copy) NSString *currencyCode;
@property (nonatomic, nullable, copy) NSString *countryCode;
@property (nonatomic, copy) NSDecimalNumber *price;
@property (nonatomic, nullable, copy) NSString *normalDuration;
@property (nonatomic, nullable, copy) NSString *introDuration;
Expand Down Expand Up @@ -46,6 +47,7 @@ @implementation RCProductInfo
- (instancetype)initWithProductIdentifier:(NSString *)productIdentifier
paymentMode:(RCPaymentMode)paymentMode
currencyCode:(NSString *)currencyCode
countryCode:(nullable NSString *)countryCode
price:(NSDecimalNumber *)price
normalDuration:(nullable NSString *)normalDuration
introDuration:(nullable NSString *)introDuration
Expand All @@ -58,6 +60,7 @@ - (instancetype)initWithProductIdentifier:(NSString *)productIdentifier
self.productIdentifier = productIdentifier;
self.paymentMode = paymentMode;
self.currencyCode = currencyCode;
self.countryCode = countryCode;
self.price = price;
self.normalDuration = normalDuration;
self.introDuration = introDuration;
Expand All @@ -77,6 +80,10 @@ - (NSDictionary *)asDictionary {
dict[@"product_id"] = self.productIdentifier;
}

if (self.countryCode) {
dict[@"store_country"] = self.countryCode;
}

if (self.price) {
dict[@"price"] = self.price;
}
Expand Down
2 changes: 1 addition & 1 deletion Purchases/Purchasing/RCProductInfoExtractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface RCProductInfoExtractor : NSObject

- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product;
- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product countryCode:(nullable NSString *)countryCode;

@end

Expand Down
3 changes: 2 additions & 1 deletion Purchases/Purchasing/RCProductInfoExtractor.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ - (instancetype)init {
return self;
}

- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product {
- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product countryCode:(nullable NSString *)countryCode {
NSString *productIdentifier = product.productIdentifier;
NSDecimalNumber *price = product.price;
NSString *currencyCode = product.priceLocale.rc_currencyCode;
Expand All @@ -51,6 +51,7 @@ - (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product {
RCProductInfo *productInfo = [[RCProductInfo alloc] initWithProductIdentifier:productIdentifier
paymentMode:paymentMode
currencyCode:currencyCode
countryCode:countryCode
price:price
normalDuration:normalDuration
introDuration:introDuration
Expand Down
7 changes: 7 additions & 0 deletions Purchases/Purchasing/RCStoreKitWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak, nullable) id<RCStoreKitWrapperDelegate> delegate;
@property (class, nonatomic, assign) BOOL simulatesAskToBuyInSandbox API_AVAILABLE(ios(8.0), macos(10.14), watchos(6.2), macCatalyst(13.0), tvos(9.0));

@property (nonatomic, readonly, nullable) SKStorefront *currentStorefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2), macCatalyst(13.1), tvos(13.0));

- (void)addPayment:(SKPayment *)payment;
- (void)finishTransaction:(SKPaymentTransaction *)transaction;
- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, macos, watchos);

- (SKMutablePayment *)paymentWithProduct:(SKProduct *)product;
- (SKMutablePayment *)paymentWithProduct:(SKProduct *)product discount:(SKPaymentDiscount *)discount API_AVAILABLE(ios(12.2), macos(10.14.4), watchos(6.2), macCatalyst(13.0), tvos(12.2));

/**
* Returns the country code for the current `SKStoreFront`, or `nil` if not available.
*/
- (nullable NSString *)countryCode;

@end

@protocol RCStoreKitWrapperDelegate
Expand Down
12 changes: 12 additions & 0 deletions Purchases/Purchasing/RCStoreKitWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ + (void)setSimulatesAskToBuyInSandbox:(BOOL)simulatesAskToBuyInSandbox {
return _delegate;
}

- (SKStorefront *)currentStorefront {
return self.paymentQueue.storefront;
}

- (NSString *)countryCode {
if (@available(iOS 13.0, macos 10.15, watchos 6.2, macCatalyst 13.1, tvos 13.0, *)) {
return self.currentStorefront.countryCode;
} else {
return nil;
}
}

- (void)addPayment:(SKPayment *)payment {
[self.paymentQueue addPayment:payment];
}
Expand Down
68 changes: 67 additions & 1 deletion PurchasesTests/Networking/BackendTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ class BackendTests: XCTestCase {
let price = 4.99 as NSDecimalNumber
let group = "sub_group"

let countryCode = "ESP"
let currencyCode = "BFD"

let paymentMode: RCPaymentMode = .none
Expand All @@ -359,6 +360,7 @@ class BackendTests: XCTestCase {
let productInfo: RCProductInfo = .createMockProductInfo(productIdentifier: productIdentifier,
paymentMode: paymentMode,
currencyCode: currencyCode,
countryCode: countryCode,
price: price,
subscriptionGroup: group)

Expand All @@ -379,6 +381,7 @@ class BackendTests: XCTestCase {
"is_restore": false,
"product_id": productIdentifier,
"price": price,
"store_country": countryCode,
"currency": currencyCode,
"subscription_group_id": group,
"presented_offering_identifier": offeringIdentifier,
Expand All @@ -395,7 +398,70 @@ class BackendTests: XCTestCase {

expect(call.path).to(equal(expectedCall.path))
expect(call.HTTPMethod).to(equal(expectedCall.HTTPMethod))
XCTAssert(call.body!.keys == expectedCall.body!.keys)
XCTAssertEqual(call.body!.keys, expectedCall.body!.keys)

expect(call.headers?["Authorization"]).toNot(beNil())
expect(call.headers?["Authorization"]).to(equal(expectedCall.headers?["Authorization"]))
}

expect(completionCalled).toEventually(beTrue())
}

func testPostsReceiptDataWithNoStorefrontCorrectly() {
let response = HTTPResponse(statusCode: 200, response: validSubscriberResponse, error: nil)
httpClient.mock(requestPath: "/receipts", response: response)

let productIdentifier = "a_great_product"
let offeringIdentifier = "a_offering"
let price = 4.99 as NSDecimalNumber
let group = "sub_group"

let currencyCode = "BFD"

let paymentMode: RCPaymentMode = .none

var completionCalled = false
let productInfo: RCProductInfo = .createMockProductInfo(productIdentifier: productIdentifier,
paymentMode: paymentMode,
currencyCode: currencyCode,
countryCode: nil,
price: price,
subscriptionGroup: group)

backend?.postReceiptData(receiptData,
appUserID: userID,
isRestore: false,
productInfo: productInfo,
presentedOfferingIdentifier: offeringIdentifier,
observerMode: false,
subscriberAttributes: nil,
completion: { (purchaserInfo, error) in
completionCalled = true
})

let body: [String: Any] = [
"app_user_id": userID,
"fetch_token": receiptData.base64EncodedString(),
"is_restore": false,
"product_id": productIdentifier,
"price": price,
"currency": currencyCode,
"subscription_group_id": group,
"presented_offering_identifier": offeringIdentifier,
"observer_mode": false
]

let expectedCall = HTTPRequest(HTTPMethod: "POST", serially: true, path: "/receipts",
body: body, headers: ["Authorization": "Bearer " + apiKey])

expect(self.httpClient.calls.count).to(equal(1))

if self.httpClient.calls.count > 0 {
let call = self.httpClient.calls[0]

expect(call.path).to(equal(expectedCall.path))
expect(call.HTTPMethod).to(equal(expectedCall.HTTPMethod))
XCTAssertEqual(call.body!.keys, expectedCall.body!.keys)

expect(call.headers?["Authorization"]).toNot(beNil())
expect(call.headers?["Authorization"]).to(equal(expectedCall.headers?["Authorization"]))
Expand Down
2 changes: 2 additions & 0 deletions PurchasesTests/Purchasing/ProductInfoExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extension RCProductInfo {
static func createMockProductInfo(productIdentifier: String = "product_id",
paymentMode: RCPaymentMode = .none,
currencyCode: String = "UYU",
countryCode: String? = nil,
price: NSDecimalNumber = 15.99,
normalDuration: String? = nil,
introDuration: String? = nil,
Expand All @@ -19,6 +20,7 @@ extension RCProductInfo {
RCProductInfo(productIdentifier: productIdentifier,
paymentMode: paymentMode,
currencyCode: currencyCode,
countryCode: countryCode,
price: price,
normalDuration: normalDuration,
introDuration: introDuration,
Expand Down