Skip to content
Open
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
157 changes: 151 additions & 6 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,15 @@ describe("Purchases", () => {
});

describe("trackCustomPaywallImpression", () => {
const presentedOfferingContext = {
offeringIdentifier: "offering",
placementIdentifier: "onboarding",
targetingContext: {
revision: 7,
ruleId: "rule_1",
},
};

describe("when Purchases is not configured", () => {
it("it rejects", async () => {
NativeModules.RNPurchases.isConfigured.mockResolvedValueOnce(false);
Expand All @@ -1999,35 +2008,171 @@ describe("Purchases", () => {
await Purchases.trackCustomPaywallImpression();

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({});
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: null,
offeringId: null,
presentedOfferingContext: null,
});
});

it("passes null values with empty params", async () => {
await Purchases.trackCustomPaywallImpression({});

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: null,
offeringId: null,
presentedOfferingContext: null,
});
});

it("makes right call with paywallId", async () => {
await Purchases.trackCustomPaywallImpression({ paywallId: "my_paywall" });

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({ paywallId: "my_paywall" });
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "my_paywall",
offeringId: null,
presentedOfferingContext: null,
});
});

it("makes right call with null paywallId", async () => {
it("sends null value with null paywallId", async () => {
await Purchases.trackCustomPaywallImpression({ paywallId: null });

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({ paywallId: null });
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: null,
offeringId: null,
presentedOfferingContext: null,
});
});

it("makes right call with offeringId only", async () => {
await Purchases.trackCustomPaywallImpression({ offeringId: "my_offering" });

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({ offeringId: "my_offering" });
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: null,
offeringId: "my_offering",
presentedOfferingContext: null,
});
});

it("sends null value with null offeringId", async () => {
await Purchases.trackCustomPaywallImpression({ offeringId: null });

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: null,
offeringId: null,
presentedOfferingContext: null,
});
});

it("makes right call with paywallId and offeringId", async () => {
await Purchases.trackCustomPaywallImpression({ paywallId: "my_paywall", offeringId: "my_offering" });

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({ paywallId: "my_paywall", offeringId: "my_offering" });
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "my_paywall",
offeringId: "my_offering",
presentedOfferingContext: null,
});
});

it("derives offering id and context from an offering", async () => {
const offering = {
identifier: "offering",
availablePackages: [
{
presentedOfferingContext,
},
],
};

await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
offering,
});

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "paywall",
offeringId: "offering",
presentedOfferingContext,
});
});

it("preserves nullable fields in offering-derived presented offering context", async () => {
const nullablePresentedOfferingContext = {
offeringIdentifier: "offering",
placementIdentifier: null,
targetingContext: null,
};
const offering = {
identifier: "offering",
availablePackages: [
{
presentedOfferingContext: nullablePresentedOfferingContext,
},
],
};

await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
offering,
});

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "paywall",
offeringId: "offering",
presentedOfferingContext: nullablePresentedOfferingContext,
});
});

it("derives offering id without context from an offering with no available packages", async () => {
const offering = {
identifier: "offering",
availablePackages: [],
};

await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
offering,
});

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "paywall",
offeringId: "offering",
presentedOfferingContext: null,
});
});

it("prefers offering-derived data over legacy offeringId", async () => {
const offering = {
identifier: "offering",
availablePackages: [
{
presentedOfferingContext,
},
],
};

await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
offering,
offeringId: "legacy_offering",
});

expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledTimes(1);
expect(NativeModules.RNPurchases.trackCustomPaywallImpression).toBeCalledWith({
paywallId: "paywall",
offeringId: "offering",
presentedOfferingContext,
});
});

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,7 @@ public void syncAttributesAndOfferingsIfNeeded(final Promise promise) {

@ReactMethod
public void setAppstackAttributionParams(ReadableMap data, final Promise promise) {
HashMap<String, Object> dataMap = new HashMap<>();
for (Map.Entry<String, Object> entry : data.toHashMap().entrySet()) {
if (entry.getValue() != null) {
dataMap.put(entry.getKey(), entry.getValue());
}
}
CommonKt.setAppstackAttributionParams(dataMap, getOnResult(promise));
CommonKt.setAppstackAttributionParams(toNonNullHashMap(data), getOnResult(promise));
}

@ReactMethod
Expand Down Expand Up @@ -633,7 +627,7 @@ public void redeemWebPurchase(String urlString, final Promise promise) {

@ReactMethod
public void trackCustomPaywallImpression(ReadableMap data) {
CommonKt.trackCustomPaywallImpression(data.toHashMap());
CommonKt.trackCustomPaywallImpression(mapWithoutNullValues(data));
}

@ReactMethod
Expand Down Expand Up @@ -741,4 +735,54 @@ private static String replacementModeFromProrationMode(int prorationMode) {
return null;
}
}

private static HashMap<String, Object> toNonNullHashMap(ReadableMap data) {
HashMap<String, Object> dataMap = new HashMap<>();
for (Map.Entry<String, Object> entry : data.toHashMap().entrySet()) {
if (entry.getValue() != null) {
dataMap.put(entry.getKey(), entry.getValue());
}
}
return dataMap;
}

private static HashMap<String, Object> mapWithoutNullValues(ReadableMap data) {
return mapWithoutNullValues(data.toHashMap());
}

private static HashMap<String, Object> mapWithoutNullValues(Map<String, Object> data) {
HashMap<String, Object> dataMap = new HashMap<>();
for (Map.Entry<String, Object> entry : data.entrySet()) {
Object value = valueWithoutNullValues(entry.getValue());
if (value != null) {
dataMap.put(entry.getKey(), value);
}
}
return dataMap;
}

private static Object valueWithoutNullValues(@Nullable Object value) {
if (value instanceof Map) {
HashMap<String, Object> dataMap = new HashMap<>();
Map<?, ?> originalMap = (Map<?, ?>) value;
for (Map.Entry<?, ?> entry : originalMap.entrySet()) {
Object filteredValue = valueWithoutNullValues(entry.getValue());
if (entry.getKey() instanceof String && filteredValue != null) {
dataMap.put((String) entry.getKey(), filteredValue);
}
}
return dataMap;
}
if (value instanceof List) {
ArrayList<Object> filteredList = new ArrayList<>();
for (Object item : (List<?>) value) {
Object filteredItem = valueWithoutNullValues(item);
if (filteredItem != null) {
filteredList.add(filteredItem);
}
}
return filteredList;
}
return value;
}
}
22 changes: 20 additions & 2 deletions apitesters/purchases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ async function checkPurchases(purchases: Purchases) {

await Purchases.presentCodeRedemptionSheet();
await Purchases.invalidateCustomerInfoCache();
await Purchases.trackCustomPaywallImpression();
await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
});
if (currentOfferingForPlacement) {
await Purchases.trackCustomPaywallImpression({
paywallId: "paywall",
offering: currentOfferingForPlacement,
});
}
}

async function checkUsers(purchases: Purchases) {
Expand Down Expand Up @@ -88,6 +98,10 @@ async function checkPurchasing(
const features: BILLING_FEATURE[] = [];
const messageTypes: IN_APP_MESSAGE_TYPE[] = [];
const googleIsPersonalizedPrice: boolean = false;
const customPaywallImpressionParams: TrackCustomPaywallImpressionOptions = {
paywallId: "paywall",
offeringId: pack.presentedOfferingContext.offeringIdentifier,
};

const paymentDiscount2: PurchasesPromotionalOffer | undefined =
await Purchases.getPromotionalOffer(product, discount);
Expand Down Expand Up @@ -175,6 +189,7 @@ async function checkPurchasing(
await Purchases.showInAppMessages(messageTypes);

const manageSubscriptions: void = await Purchases.showManageSubscriptions();
await Purchases.trackCustomPaywallImpression(customPaywallImpressionParams);
}

async function checkConfigure() {
Expand Down Expand Up @@ -484,11 +499,14 @@ async function checkTrackCustomPaywallImpression() {
await Purchases.trackCustomPaywallImpression({ offeringId: "my_offering" });
await Purchases.trackCustomPaywallImpression({ offeringId: null });
await Purchases.trackCustomPaywallImpression({ paywallId: "my_paywall", offeringId: "my_offering" });
const options: TrackCustomPaywallImpressionOptions = { paywallId: "test", offeringId: "offering" };
const options: TrackCustomPaywallImpressionOptions = {
paywallId: "test",
offeringId: "offering",
};
await Purchases.trackCustomPaywallImpression(options);
}

async function checkSyncPurchasesResult() {
const syncPurchasesResult: SyncPurchasesResult = await Purchases.syncPurchasesForResult();
const customerInfo: CustomerInfo = syncPurchasesResult.customerInfo;
}
}
Loading