@livx.cc/appwrap 0.53.0 → 0.54.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/appwrap",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Wrap any PWA into a native app with native capabilities (appwrap runtime + @livx.cc/native-kit).",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -27,6 +27,7 @@ declare const com: any;
27
27
  */
28
28
 
29
29
  const PERIOD_UNIT = ['D', 'W', 'M', 'Y']; // SKProductPeriodUnit: Day/Week/Month/Year
30
+ const DISCOUNT_FREE_TRIAL = 2; // SKProductDiscountPaymentMode.freeTrial (0=PayAsYouGo, 1=PayUpFront, 2=FreeTrial)
30
31
  const STATE = { Purchasing: 0, Purchased: 1, Failed: 2, Restored: 3, Deferred: 4 };
31
32
  const SK_ERR_CANCELLED = 2; // SKErrorPaymentCancelled
32
33
 
@@ -46,12 +47,40 @@ function appReceipt(): string {
46
47
  return data ? data.base64EncodedStringWithOptions(0 as unknown as NSDataBase64EncodingOptions) : '';
47
48
  }
48
49
 
50
+ /**
51
+ * SKProductDiscount → the platform-neutral IntroOffer (parity with the Android mapper's zero-price
52
+ * phase, same `{ price, displayPrice, period, isFreeTrial }` shape native-kit's `IntroOffer` types).
53
+ *
54
+ * ⚠️ ELIGIBILITY CAVEAT: StoreKit 1 exposes `introductoryPrice` on the SKProduct REGARDLESS of whether
55
+ * this user is eligible — SK1 has no eligibility API (SK2's `isEligibleForIntroOffer` is Swift-async,
56
+ * unreachable via the ObjC bridge this handler uses). So a user who already consumed a trial in this
57
+ * group MAY see trial copy they won't actually get; StoreKit's own purchase sheet shows the truth at
58
+ * checkout. Gating on real eligibility is a follow-up if/when this handler moves to StoreKit 2.
59
+ */
60
+ function mapIntroOffer(d: SKProductDiscount, locale: NSLocale): {
61
+ price: number; displayPrice: string; period: string | undefined; isFreeTrial: boolean;
62
+ } {
63
+ const f = NSNumberFormatter.new();
64
+ f.numberStyle = 2; // CurrencyStyle
65
+ f.locale = locale;
66
+ const sp = d.subscriptionPeriod;
67
+ // The discount period is ONE cycle; numberOfPeriods is how many cycles it repeats. Total trial
68
+ // length = numberOfUnits × numberOfPeriods (e.g. 1 week × 1 = P1W).
69
+ const totalUnits = (sp ? sp.numberOfUnits : 0) * (d.numberOfPeriods || 1);
70
+ return {
71
+ price: d.price ? d.price.doubleValue : 0,
72
+ displayPrice: String(f.stringFromNumber(d.price as unknown as number) ?? ''),
73
+ period: sp ? `P${totalUnits}${PERIOD_UNIT[sp.unit] ?? 'D'}` : undefined,
74
+ isFreeTrial: d.paymentMode === DISCOUNT_FREE_TRIAL || (d.price ? d.price.doubleValue : 0) === 0,
75
+ };
76
+ }
77
+
49
78
  function mapProduct(p: SKProduct) {
50
79
  const f = NSNumberFormatter.new();
51
80
  f.numberStyle = 2; // CurrencyStyle
52
81
  f.locale = p.priceLocale;
53
82
  const sp = p.subscriptionPeriod;
54
- return {
83
+ const product: any = {
55
84
  id: String(p.productIdentifier),
56
85
  title: String(p.localizedTitle ?? ''),
57
86
  description: String(p.localizedDescription ?? ''),
@@ -62,6 +91,10 @@ function mapProduct(p: SKProduct) {
62
91
  type: sp ? 'autoRenewable' : 'unknown',
63
92
  subscriptionPeriod: sp ? `P${sp.numberOfUnits}${PERIOD_UNIT[sp.unit] ?? 'D'}` : undefined,
64
93
  };
94
+ // Surface the intro / free-trial phase so the paywall can show trial terms (see eligibility caveat
95
+ // on mapIntroOffer). Populated only when StoreKit reports an introductory price for this product.
96
+ if (p.introductoryPrice) product.introOffer = mapIntroOffer(p.introductoryPrice, p.priceLocale);
97
+ return product;
65
98
  }
66
99
 
67
100
  function receiptFor(productId: string, transactionId?: string) {