Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
57 changes: 55 additions & 2 deletions QonversionTests/QRequestSerializerTests.m
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#import <XCTest/XCTest.h>
#import "QNRequestSerializer.h"
#import "QONStoreKit2PurchaseModel.h"

@interface QNRequestSerializerTests : XCTestCase

@property (nonatomic, strong) QNRequestSerializer *serializer;

@end

@implementation QNRequestSerializerTests

- (void)setUp {
[super setUp];

self.serializer = [[QNRequestSerializer alloc] init];
}

Expand All @@ -21,4 +22,56 @@ - (void)testThatLaunchDataCorrect {
XCTAssertNotNil(launchData);
}

- (QONStoreKit2PurchaseModel *)baseStoreKit2Model {
QONStoreKit2PurchaseModel *model = [QONStoreKit2PurchaseModel new];
model.productId = @"com.qonversion.test.monthly";
model.price = @"9.99";
model.currency = @"USD";
model.transactionId = @"1000000000000001";
model.originalTransactionId = @"1000000000000001";
model.subscriptionPeriodUnit = @"2";
model.subscriptionPeriodNumberOfUnits = @"1";

return model;
}

- (void)testThatStoreKit2PaidIntroReportsIntroductoryPrice {
QONStoreKit2PurchaseModel *model = [self baseStoreKit2Model];
model.introductoryPrice = @"1.99";
model.introductoryNumberOfPeriods = @"1";
model.introductoryPeriodNumberOfUnits = @"1";
model.introductoryPeriodUnit = @"2";
model.introductoryPaymentMode = @"1";

NSDictionary *data = [self.serializer purchaseInfo:model receipt:nil];
NSDictionary *introOffer = data[@"introductory_offer"];

XCTAssertNotNil(introOffer);
XCTAssertEqualObjects(introOffer[@"value"], @"1.99");
XCTAssertNotEqualObjects(introOffer[@"value"], model.price);
}

- (void)testThatStoreKit2FreeTrialReportsZeroIntroductoryPrice {
QONStoreKit2PurchaseModel *model = [self baseStoreKit2Model];
model.introductoryPrice = @"0";
model.introductoryNumberOfPeriods = @"1";
model.introductoryPeriodNumberOfUnits = @"7";
model.introductoryPeriodUnit = @"0";
model.introductoryPaymentMode = @"2";

NSDictionary *data = [self.serializer purchaseInfo:model receipt:nil];
NSDictionary *introOffer = data[@"introductory_offer"];

XCTAssertNotNil(introOffer);
XCTAssertEqualObjects(introOffer[@"value"], @"0");
}

- (void)testThatStoreKit2NoIntroOmitsIntroductoryOffer {
QONStoreKit2PurchaseModel *model = [self baseStoreKit2Model];

NSDictionary *data = [self.serializer purchaseInfo:model receipt:nil];

XCTAssertNil(data[@"introductory_offer"]);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,17 @@ - (NSDictionary *)purchaseInfo:(QONStoreKit2PurchaseModel *)purchaseModel
purchaseDict[@"period_unit"] = purchaseModel.subscriptionPeriodUnit;
purchaseDict[@"period_number_of_units"] = purchaseModel.subscriptionPeriodNumberOfUnits;

NSMutableDictionary *introOffer = [[NSMutableDictionary alloc] init];

introOffer[@"value"] = purchaseModel.price;
introOffer[@"number_of_periods"] = purchaseModel.introductoryNumberOfPeriods;
introOffer[@"period_number_of_units"] = purchaseModel.introductoryPeriodNumberOfUnits;
introOffer[@"period_unit"] = purchaseModel.introductoryPeriodUnit;
introOffer[@"payment_mode"] = purchaseModel.introductoryPaymentMode;

result[@"introductory_offer"] = introOffer.count > 0 ? introOffer : nil;
if (purchaseModel.introductoryPrice != nil) {

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.

Behavioral note: this guard also changes the no-offer case. Previously an introductory_offer block was emitted on every purchase here, because the old introOffer.count > 0 check was always true (the value was set from price, which is nonnull). With this guard, purchases without an introductory offer omit the block entirely.

That matches the StoreKit 1 path above (purchaseData: guards on product.introductoryPrice != nil), so it should be safe - just flagging that the request body shape changes for no-offer StoreKit 2 purchases, in case anything consuming the payload expects the block to always be present.

NSMutableDictionary *introOffer = [[NSMutableDictionary alloc] init];

introOffer[@"value"] = purchaseModel.introductoryPrice;
introOffer[@"number_of_periods"] = purchaseModel.introductoryNumberOfPeriods;
introOffer[@"period_number_of_units"] = purchaseModel.introductoryPeriodNumberOfUnits;
introOffer[@"period_unit"] = purchaseModel.introductoryPeriodUnit;
introOffer[@"payment_mode"] = purchaseModel.introductoryPaymentMode;

result[@"introductory_offer"] = introOffer;
}

NSMutableDictionary *promoOffer = [[NSMutableDictionary alloc] init];

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.

Minor, and out of scope for this fix: the promo_offer block is still built and assigned unconditionally (result[@"promo_offer"] = [promoOffer copy] a few lines down), so a purchase with no promotional offer sends an empty promo_offer: {}. That is the same always-emitted-block pattern this PR just fixed for introductory_offer.

The StoreKit 1 path guards the equivalent block on offerId.length > 0. Might be worth a follow-up to guard this on promoOfferId != nil for parity.

promoOffer[@"id"] = purchaseModel.promoOfferId;
Expand Down
Loading