@mysuperwall/react-native 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 - 2026-10-15
4
+
5
+ - Add setProductData for real localized billing prices and currency.
6
+ - Scope rendered HTML caches by the exact price payload, rejecting late responses from previous price contexts.
7
+ - Keep existing billing and store purchase flows owned by the host application.
8
+
3
9
  ## 1.1.0
4
10
 
5
11
  - Add compiled JavaScript and TypeScript declarations to npm packages.
package/README.md CHANGED
@@ -156,3 +156,19 @@ Programmatic dismissal through the hook clears the modal without starting recove
156
156
  Manifest and offline event storage are isolated by app/server scope. Unscoped legacy
157
157
  caches are ignored; source updates need one successful manifest sync before offline use.
158
158
  No store billing is replaced by this SDK.
159
+
160
+ ## Localized store prices (1.2.0+)
161
+
162
+ This SDK does not query StoreKit, Google Play Billing or RevenueCat products itself.
163
+ Initialize the existing billing provider and fetch its products first. Build a map
164
+ keyed by the exact store product ID. Each value must contain `displayPrice`,
165
+ `priceString` and `currencyCode` from the store. Supply `subscriptionPeriod` and
166
+ store-derived `pricePerDayText`, `pricePerWeekText`, `pricePerMonthText`,
167
+ `pricePerYearText` where the template uses them. Never convert a USD catalog price
168
+ using device locale or exchange rates. Storefront determines the currency.
169
+
170
+ Call `setProductData` after SDK initialization and before presenting. Await its
171
+ completion (Promise on React Native, callback on native platforms) and refresh it
172
+ when store product prices change. Missing live product data is not proof that
173
+ catalog/default prices are localized. Wait for successful manifest loading on a
174
+ first installation. Existing displayed paywalls are not silently replaced.
@@ -23,6 +23,7 @@ export declare class MySuperwall {
23
23
  country: string;
24
24
  appVersion?: string;
25
25
  private premium;
26
+ private productData;
26
27
  private epoch;
27
28
  private presentationSequence;
28
29
  private followUps;
@@ -42,6 +43,8 @@ export declare class MySuperwall {
42
43
  private resetFlow;
43
44
  private manifestOptions;
44
45
  setLocale(locale: string): Promise<void>;
46
+ /** Supply localized prices from StoreKit, Play Billing or RevenueCat before presenting. */
47
+ setProductData(products: Record<string, unknown>): Promise<void>;
45
48
  setCountry(country: string): void;
46
49
  setAppVersion(version: string): void;
47
50
  setSubscriptionStatus(premium: boolean): void;
@@ -13,6 +13,7 @@ class MySuperwall {
13
13
  this.locale = 'en';
14
14
  this.country = '';
15
15
  this.premium = false;
16
+ this.productData = {};
16
17
  this.epoch = 0;
17
18
  this.presentationSequence = 0;
18
19
  this.followUps = new followUpService_1.FollowUpService();
@@ -35,6 +36,7 @@ class MySuperwall {
35
36
  inst.resetFlow();
36
37
  inst.userAttributes = {};
37
38
  inst.premium = false;
39
+ inst.productData = {};
38
40
  inst.appKey = options.appKey;
39
41
  inst.baseUrl = options.baseUrl
40
42
  ? options.baseUrl.endsWith('/')
@@ -78,7 +80,7 @@ class MySuperwall {
78
80
  }
79
81
  resetFlow() { this.epoch++; this.followUps.reset(); this.activeFlow = undefined; }
80
82
  manifestOptions() {
81
- return { baseUrl: this.baseUrl, appKey: this.appKey, bundleId: this.bundleId, platform: this.platform, lang: this.locale };
83
+ return { baseUrl: this.baseUrl, appKey: this.appKey, bundleId: this.bundleId, platform: this.platform, lang: this.locale, substitutionData: this.productData };
82
84
  }
83
85
  async setLocale(locale) {
84
86
  const normalized = locale.trim().replace(/_/g, '-').toLowerCase();
@@ -92,6 +94,16 @@ class MySuperwall {
92
94
  if (epoch === this.epoch)
93
95
  await this.manifestService.syncInBackground(this.manifestOptions());
94
96
  }
97
+ /** Supply localized prices from StoreKit, Play Billing or RevenueCat before presenting. */
98
+ async setProductData(products) {
99
+ const captured = JSON.parse(JSON.stringify(products));
100
+ if (JSON.stringify(captured) === JSON.stringify(this.productData))
101
+ return;
102
+ this.productData = captured;
103
+ const options = this.manifestOptions();
104
+ this.manifestService.configure(options); // Drops old price HTML and rejects stale responses.
105
+ await this.manifestService.syncInBackground(options);
106
+ }
95
107
  setCountry(country) { this.country = country.trim().toUpperCase(); }
96
108
  setAppVersion(version) { this.appVersion = version; }
97
109
  setSubscriptionStatus(premium) { this.premium = premium; }
@@ -5,6 +5,7 @@ export interface ManifestScope {
5
5
  bundleId?: string;
6
6
  platform?: string;
7
7
  lang?: string;
8
+ substitutionData?: Record<string, unknown>;
8
9
  }
9
10
  export declare class ManifestService {
10
11
  private scope;
@@ -14,7 +14,7 @@ class ManifestService {
14
14
  }
15
15
  static scopeKey(options) {
16
16
  return JSON.stringify([options.baseUrl.replace(/\/+$/, '') + '/', options.appKey || '',
17
- options.bundleId || '', options.platform || 'ios', options.lang || 'en']);
17
+ options.bundleId || '', options.platform || 'ios', options.lang || 'en', JSON.stringify(options.substitutionData || {})]);
18
18
  }
19
19
  configure(options) {
20
20
  const scope = ManifestService.scopeKey(options);
@@ -59,7 +59,7 @@ class ManifestService {
59
59
  const response = await fetch(options.baseUrl.replace(/\/+$/, '') + '/paywall/manifest', {
60
60
  method: 'POST', headers, body: JSON.stringify({ platform: options.platform || 'ios',
61
61
  appKey: options.appKey, bundleId: options.platform === 'android' ? undefined : options.bundleId,
62
- packageName: options.platform === 'android' ? options.bundleId : undefined, lang: options.lang || 'en', hash: this.manifest?.hash })
62
+ packageName: options.platform === 'android' ? options.bundleId : undefined, lang: options.lang || 'en', hash: this.manifest?.hash, substitutionData: options.substitutionData || {} })
63
63
  });
64
64
  if (scope !== this.scope || generation !== this.generation)
65
65
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mysuperwall/react-native",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "React Native SDK for MySuperWall paywalls, targeting and offline rule evaluation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -45,7 +45,7 @@
45
45
  "homepage": "https://mysuperwall.com",
46
46
  "repository": {
47
47
  "type": "git",
48
- "url": "https://github.com/Unevenn/mysuperwall_react_native.git"
48
+ "url": "git+https://github.com/Unevenn/mysuperwall_react_native.git"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
@@ -28,6 +28,7 @@ export class MySuperwall {
28
28
  public country: string = '';
29
29
  public appVersion?: string;
30
30
  private premium = false;
31
+ private productData: Record<string, unknown> = {};
31
32
  private epoch = 0;
32
33
  private presentationSequence = 0;
33
34
  private followUps = new FollowUpService();
@@ -61,6 +62,7 @@ export class MySuperwall {
61
62
  inst.resetFlow();
62
63
  inst.userAttributes = {};
63
64
  inst.premium = false;
65
+ inst.productData = {};
64
66
  inst.appKey = options.appKey;
65
67
  inst.baseUrl = options.baseUrl
66
68
  ? options.baseUrl.endsWith('/')
@@ -102,7 +104,7 @@ export class MySuperwall {
102
104
 
103
105
  private resetFlow(): void { this.epoch++; this.followUps.reset(); this.activeFlow = undefined; }
104
106
  private manifestOptions() {
105
- return { baseUrl: this.baseUrl, appKey: this.appKey, bundleId: this.bundleId, platform: this.platform, lang: this.locale };
107
+ return { baseUrl: this.baseUrl, appKey: this.appKey, bundleId: this.bundleId, platform: this.platform, lang: this.locale, substitutionData: this.productData };
106
108
  }
107
109
  public async setLocale(locale: string): Promise<void> {
108
110
  const normalized = locale.trim().replace(/_/g, '-').toLowerCase();
@@ -113,6 +115,16 @@ export class MySuperwall {
113
115
  await this.manifestService.loadFromDisk();
114
116
  if (epoch === this.epoch) await this.manifestService.syncInBackground(this.manifestOptions());
115
117
  }
118
+ /** Supply localized prices from StoreKit, Play Billing or RevenueCat before presenting. */
119
+ public async setProductData(products: Record<string, unknown>): Promise<void> {
120
+ const captured = JSON.parse(JSON.stringify(products)) as Record<string, unknown>;
121
+ if (JSON.stringify(captured) === JSON.stringify(this.productData)) return;
122
+ this.productData = captured;
123
+ const options = this.manifestOptions();
124
+ this.manifestService.configure(options); // Drops old price HTML and rejects stale responses.
125
+ await this.manifestService.syncInBackground(options);
126
+ }
127
+
116
128
  public setCountry(country: string): void { this.country = country.trim().toUpperCase(); }
117
129
  public setAppVersion(version: string): void { this.appVersion = version; }
118
130
  public setSubscriptionStatus(premium: boolean): void { this.premium = premium; }
@@ -1,7 +1,7 @@
1
1
  import { PaywallManifest } from '../models/manifest';
2
2
 
3
3
  export interface ManifestScope {
4
- baseUrl: string; appKey?: string; bundleId?: string; platform?: string; lang?: string;
4
+ baseUrl: string; appKey?: string; bundleId?: string; platform?: string; lang?: string; substitutionData?: Record<string, unknown>;
5
5
  }
6
6
 
7
7
  export class ManifestService {
@@ -15,7 +15,7 @@ export class ManifestService {
15
15
  }
16
16
  static scopeKey(options: ManifestScope): string {
17
17
  return JSON.stringify([options.baseUrl.replace(/\/+$/, '') + '/', options.appKey || '',
18
- options.bundleId || '', options.platform || 'ios', options.lang || 'en']);
18
+ options.bundleId || '', options.platform || 'ios', options.lang || 'en', JSON.stringify(options.substitutionData || {})]);
19
19
  }
20
20
  configure(options: ManifestScope): void {
21
21
  const scope = ManifestService.scopeKey(options);
@@ -48,7 +48,7 @@ export class ManifestService {
48
48
  const response = await fetch(options.baseUrl.replace(/\/+$/, '') + '/paywall/manifest', {
49
49
  method: 'POST', headers, body: JSON.stringify({ platform: options.platform || 'ios',
50
50
  appKey: options.appKey, bundleId: options.platform === 'android' ? undefined : options.bundleId,
51
- packageName: options.platform === 'android' ? options.bundleId : undefined, lang: options.lang || 'en', hash: this.manifest?.hash })
51
+ packageName: options.platform === 'android' ? options.bundleId : undefined, lang: options.lang || 'en', hash: this.manifest?.hash, substitutionData: options.substitutionData || {} })
52
52
  });
53
53
  if (scope !== this.scope || generation !== this.generation) return null;
54
54
  if (response.status === 304) return this.manifest;