@payvia-sdk/sdk 1.1.4 → 1.3.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.
Files changed (4) hide show
  1. package/README.md +19 -1
  2. package/package.json +18 -4
  3. package/payvia.d.ts +313 -0
  4. package/payvia.js +193 -50
package/README.md CHANGED
@@ -23,9 +23,27 @@ Copy `payvia.js` into your extension folder.
23
23
  ### Option 2: npm
24
24
 
25
25
  ```bash
26
- npm install payvia-sdk
26
+ npm install @payvia-sdk/sdk
27
27
  ```
28
28
 
29
+ ## TypeScript Support
30
+
31
+ The SDK ships with built-in TypeScript declarations - no `@types/*` package needed:
32
+
33
+ ```typescript
34
+ import PayVia from '@payvia-sdk/sdk';
35
+ import type { PayViaUser, Tier } from '@payvia-sdk/sdk';
36
+
37
+ const payvia = PayVia(process.env.PAYVIA_API_KEY!);
38
+
39
+ const user: PayViaUser = await payvia.getUser();
40
+ if (user.tier && user.tier.level >= 1) {
41
+ // Pro or above
42
+ }
43
+ ```
44
+
45
+ All methods, options, and return types have full IntelliSense support.
46
+
29
47
  ## Basic Usage
30
48
 
31
49
  ### 1. Add to manifest.json
package/package.json CHANGED
@@ -1,23 +1,37 @@
1
1
  {
2
2
  "name": "@payvia-sdk/sdk",
3
- "version": "1.1.4",
4
- "description": "A lightweight JavaScript SDK for connecting your Chrome Extension / SaaS app to PayVia, for accepting PayPal payments and managing subscriptions, license validation, tier-based feature gating, trial management and monthly / yearly / lifetime plans",
3
+ "version": "1.3.0",
4
+ "description": "A lightweight JavaScript SDK for connecting your Chrome Extension / SaaS app to PayVia, for accepting PayPal and Tranzila payments and managing subscriptions, license validation, tier-based feature gating, trial management and monthly / yearly / lifetime plans",
5
5
  "main": "payvia.js",
6
+ "types": "payvia.d.ts",
6
7
  "type": "module",
7
8
  "exports": {
8
- ".": "./payvia.js"
9
+ ".": {
10
+ "types": "./payvia.d.ts",
11
+ "import": "./payvia.js",
12
+ "default": "./payvia.js"
13
+ }
9
14
  },
10
15
  "files": [
11
16
  "payvia.js",
17
+ "payvia.d.ts",
12
18
  "README.md"
13
19
  ],
20
+ "scripts": {
21
+ "build:types": "tsc",
22
+ "prepublishOnly": "npm run build:types"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.6.0"
26
+ },
14
27
  "keywords": [
15
28
  "paypal",
16
29
  "payments",
17
30
  "chrome-extension",
18
31
  "saas",
19
32
  "subscriptions",
20
- "monetization"
33
+ "monetization",
34
+ "typescript"
21
35
  ],
22
36
  "author": "PayVia",
23
37
  "license": "MIT",
package/payvia.d.ts ADDED
@@ -0,0 +1,313 @@
1
+ export default PayVia;
2
+ export type Tier = {
3
+ id: string;
4
+ name: string;
5
+ /**
6
+ * - 0=Free, 1=Pro, 2=Super
7
+ */
8
+ level: number;
9
+ features: string[];
10
+ };
11
+ export type Identity = {
12
+ /**
13
+ * - Unique user identifier (email or random pv_* ID)
14
+ */
15
+ id: string;
16
+ /**
17
+ * - User email (if known)
18
+ */
19
+ email: string | null;
20
+ /**
21
+ * - How the identity was obtained
22
+ */
23
+ source: "google" | "random";
24
+ };
25
+ export type PayViaUser = {
26
+ id: string;
27
+ email: string | null;
28
+ identitySource: "google" | "random";
29
+ /**
30
+ * - True if status is ACTIVE or TRIAL
31
+ */
32
+ paid: boolean;
33
+ status: "ACTIVE" | "TRIAL" | "INACTIVE";
34
+ tier: Tier | null;
35
+ /**
36
+ * - Shortcut to tier.features
37
+ */
38
+ features: string[];
39
+ planIds: string[];
40
+ isTrial: boolean;
41
+ trialExpiresAt?: Date | null;
42
+ daysRemaining?: number | null;
43
+ canceledAt?: Date | null;
44
+ currentPeriodEnd?: Date | null;
45
+ isCanceled?: boolean;
46
+ cancelGraceActive?: boolean;
47
+ /**
48
+ * - True if data came from cache
49
+ */
50
+ fromCache: boolean;
51
+ /**
52
+ * - Unix timestamp in ms
53
+ */
54
+ checkedAt?: number | null;
55
+ /**
56
+ * - Cache TTL in ms
57
+ */
58
+ ttl?: number | null;
59
+ /**
60
+ * - HMAC anti-tamper signature
61
+ */
62
+ signature?: string | null;
63
+ /**
64
+ * - Set only when a network error occurred and no valid cache existed
65
+ */
66
+ error?: string;
67
+ };
68
+ export type TrialStartResult = {
69
+ subscriptionId: string;
70
+ status: string;
71
+ planId: string;
72
+ planName: string;
73
+ trialExpiresAt: Date;
74
+ daysRemaining: number;
75
+ };
76
+ export type TrialStatus = {
77
+ status: string;
78
+ trialExpiresAt: Date | null;
79
+ daysRemaining: number | null;
80
+ canConvert: boolean;
81
+ planIds: string[];
82
+ };
83
+ export type Plan = {
84
+ id: string;
85
+ name: string;
86
+ description?: string;
87
+ price?: number;
88
+ currency?: string;
89
+ interval?: string;
90
+ tierId?: string;
91
+ };
92
+ export type OpenPaymentPageOptions = {
93
+ /**
94
+ * - Checkout mode (default: 'pricing')
95
+ */
96
+ mode?: "pricing" | "hosted" | "direct";
97
+ /**
98
+ * - Required for 'hosted' and 'direct' modes
99
+ */
100
+ planId?: string;
101
+ /**
102
+ * - Customer email (optional; PayPal collects it for anonymous users)
103
+ */
104
+ email?: string;
105
+ /**
106
+ * - Redirect URL after successful payment ('direct' mode only)
107
+ */
108
+ successUrl?: string;
109
+ /**
110
+ * - Redirect URL if user cancels ('direct' mode only)
111
+ */
112
+ cancelUrl?: string;
113
+ };
114
+ export type OpenPaymentPageResult = {
115
+ /**
116
+ * - 'pricing' | 'hosted' for those modes; null/'iframe'/'redirect' for 'direct' mode (reflects PayVia backend response shape)
117
+ */
118
+ mode?: string | null;
119
+ pricingUrl?: string;
120
+ checkoutUrl?: string;
121
+ sessionId?: string;
122
+ /**
123
+ * - 'PayPal' | 'Tranzila'
124
+ */
125
+ provider?: string;
126
+ };
127
+ export type CancelSubscriptionOptions = {
128
+ /**
129
+ * - Specific plan to cancel
130
+ */
131
+ planId?: string;
132
+ /**
133
+ * - Cancellation reason
134
+ */
135
+ reason?: string;
136
+ };
137
+ export type CancelSubscriptionResult = {
138
+ success: boolean;
139
+ message: string;
140
+ canceledPlanId?: string;
141
+ };
142
+ export type ResetLicenseResult = {
143
+ message: string;
144
+ };
145
+ export type OnPaidCallback = (user: PayViaUser) => void;
146
+ export type PayViaInstance = {
147
+ getUser: (options?: {
148
+ forceRefresh?: boolean;
149
+ }) => Promise<PayViaUser>;
150
+ refresh: () => Promise<PayViaUser>;
151
+ refreshLicenseCache: () => Promise<void>;
152
+ hasFeature: (featureName: string) => Promise<boolean>;
153
+ hasTierLevel: (requiredLevel: number) => Promise<boolean>;
154
+ getTier: () => Promise<Tier | null>;
155
+ startTrial: () => Promise<TrialStartResult | null>;
156
+ getTrialStatus: () => Promise<TrialStatus>;
157
+ isFirstRun: () => Promise<boolean>;
158
+ markFirstRunDone: () => Promise<void>;
159
+ needsEmailForPayment: () => Promise<boolean>;
160
+ getIdentity: () => Promise<Identity>;
161
+ openPaymentPage: (options?: OpenPaymentPageOptions) => Promise<OpenPaymentPageResult>;
162
+ onPaid: (callback: OnPaidCallback) => (() => void);
163
+ getPlans: () => Promise<Plan[]>;
164
+ resetLicense: () => Promise<ResetLicenseResult>;
165
+ cancelSubscription: (options?: CancelSubscriptionOptions) => Promise<CancelSubscriptionResult>;
166
+ };
167
+ /**
168
+ * PayVia SDK for Chrome Extensions
169
+ *
170
+ * ספריית JavaScript שהסולק מטמיע בתוסף הכרום שלו
171
+ * מאפשרת בדיקת רישיון, פתיחת חלון תשלום, וניהול מנויים
172
+ *
173
+ * Usage:
174
+ * ```javascript
175
+ * import PayVia from './payvia.js';
176
+ *
177
+ * const payvia = PayVia('YOUR_API_KEY');
178
+ *
179
+ * // Check if user paid
180
+ * const user = await payvia.getUser();
181
+ * if (user.paid) {
182
+ * // Enable premium features
183
+ * }
184
+ *
185
+ * // Open payment page
186
+ * payvia.openPaymentPage();
187
+ * ```
188
+ */
189
+ /**
190
+ * @typedef {Object} Tier
191
+ * @property {string} id
192
+ * @property {string} name
193
+ * @property {number} level - 0=Free, 1=Pro, 2=Super
194
+ * @property {string[]} features
195
+ */
196
+ /**
197
+ * @typedef {Object} Identity
198
+ * @property {string} id - Unique user identifier (email or random pv_* ID)
199
+ * @property {string | null} email - User email (if known)
200
+ * @property {'google' | 'random'} source - How the identity was obtained
201
+ */
202
+ /**
203
+ * @typedef {Object} PayViaUser
204
+ * @property {string} id
205
+ * @property {string | null} email
206
+ * @property {'google' | 'random'} identitySource
207
+ * @property {boolean} paid - True if status is ACTIVE or TRIAL
208
+ * @property {'ACTIVE' | 'TRIAL' | 'INACTIVE'} status
209
+ * @property {Tier | null} tier
210
+ * @property {string[]} features - Shortcut to tier.features
211
+ * @property {string[]} planIds
212
+ * @property {boolean} isTrial
213
+ * @property {Date | null} [trialExpiresAt]
214
+ * @property {number | null} [daysRemaining]
215
+ * @property {Date | null} [canceledAt]
216
+ * @property {Date | null} [currentPeriodEnd]
217
+ * @property {boolean} [isCanceled]
218
+ * @property {boolean} [cancelGraceActive]
219
+ * @property {boolean} fromCache - True if data came from cache
220
+ * @property {number | null} [checkedAt] - Unix timestamp in ms
221
+ * @property {number | null} [ttl] - Cache TTL in ms
222
+ * @property {string | null} [signature] - HMAC anti-tamper signature
223
+ * @property {string} [error] - Set only when a network error occurred and no valid cache existed
224
+ */
225
+ /**
226
+ * @typedef {Object} TrialStartResult
227
+ * @property {string} subscriptionId
228
+ * @property {string} status
229
+ * @property {string} planId
230
+ * @property {string} planName
231
+ * @property {Date} trialExpiresAt
232
+ * @property {number} daysRemaining
233
+ */
234
+ /**
235
+ * @typedef {Object} TrialStatus
236
+ * @property {string} status
237
+ * @property {Date | null} trialExpiresAt
238
+ * @property {number | null} daysRemaining
239
+ * @property {boolean} canConvert
240
+ * @property {string[]} planIds
241
+ */
242
+ /**
243
+ * @typedef {Object} Plan
244
+ * @property {string} id
245
+ * @property {string} name
246
+ * @property {string} [description]
247
+ * @property {number} [price]
248
+ * @property {string} [currency]
249
+ * @property {string} [interval]
250
+ * @property {string} [tierId]
251
+ */
252
+ /**
253
+ * @typedef {Object} OpenPaymentPageOptions
254
+ * @property {'pricing' | 'hosted' | 'direct'} [mode] - Checkout mode (default: 'pricing')
255
+ * @property {string} [planId] - Required for 'hosted' and 'direct' modes
256
+ * @property {string} [email] - Customer email (optional; PayPal collects it for anonymous users)
257
+ * @property {string} [successUrl] - Redirect URL after successful payment ('direct' mode only)
258
+ * @property {string} [cancelUrl] - Redirect URL if user cancels ('direct' mode only)
259
+ */
260
+ /**
261
+ * @typedef {Object} OpenPaymentPageResult
262
+ * @property {string | null} [mode] - 'pricing' | 'hosted' for those modes; null/'iframe'/'redirect' for 'direct' mode (reflects PayVia backend response shape)
263
+ * @property {string} [pricingUrl]
264
+ * @property {string} [checkoutUrl]
265
+ * @property {string} [sessionId]
266
+ * @property {string} [provider] - 'PayPal' | 'Tranzila'
267
+ */
268
+ /**
269
+ * @typedef {Object} CancelSubscriptionOptions
270
+ * @property {string} [planId] - Specific plan to cancel
271
+ * @property {string} [reason] - Cancellation reason
272
+ */
273
+ /**
274
+ * @typedef {Object} CancelSubscriptionResult
275
+ * @property {boolean} success
276
+ * @property {string} message
277
+ * @property {string} [canceledPlanId]
278
+ */
279
+ /**
280
+ * @typedef {Object} ResetLicenseResult
281
+ * @property {string} message
282
+ */
283
+ /**
284
+ * @callback OnPaidCallback
285
+ * @param {PayViaUser} user
286
+ * @returns {void}
287
+ */
288
+ /**
289
+ * @typedef {Object} PayViaInstance
290
+ * @property {(options?: { forceRefresh?: boolean }) => Promise<PayViaUser>} getUser
291
+ * @property {() => Promise<PayViaUser>} refresh
292
+ * @property {() => Promise<void>} refreshLicenseCache
293
+ * @property {(featureName: string) => Promise<boolean>} hasFeature
294
+ * @property {(requiredLevel: number) => Promise<boolean>} hasTierLevel
295
+ * @property {() => Promise<Tier | null>} getTier
296
+ * @property {() => Promise<TrialStartResult | null>} startTrial
297
+ * @property {() => Promise<TrialStatus>} getTrialStatus
298
+ * @property {() => Promise<boolean>} isFirstRun
299
+ * @property {() => Promise<void>} markFirstRunDone
300
+ * @property {() => Promise<boolean>} needsEmailForPayment
301
+ * @property {() => Promise<Identity>} getIdentity
302
+ * @property {(options?: OpenPaymentPageOptions) => Promise<OpenPaymentPageResult>} openPaymentPage
303
+ * @property {(callback: OnPaidCallback) => (() => void)} onPaid
304
+ * @property {() => Promise<Plan[]>} getPlans
305
+ * @property {() => Promise<ResetLicenseResult>} resetLicense
306
+ * @property {(options?: CancelSubscriptionOptions) => Promise<CancelSubscriptionResult>} cancelSubscription
307
+ */
308
+ /**
309
+ * Create a PayVia SDK instance.
310
+ * @param {string} apiKey - API key from the PayVia dashboard
311
+ * @returns {PayViaInstance} PayVia SDK instance
312
+ */
313
+ declare function PayVia(apiKey: string): PayViaInstance;
package/payvia.js CHANGED
@@ -1,26 +1,166 @@
1
1
  /**
2
2
  * PayVia SDK for Chrome Extensions
3
- *
3
+ *
4
4
  * ספריית JavaScript שהסולק מטמיע בתוסף הכרום שלו
5
5
  * מאפשרת בדיקת רישיון, פתיחת חלון תשלום, וניהול מנויים
6
- *
6
+ *
7
7
  * Usage:
8
8
  * ```javascript
9
9
  * import PayVia from './payvia.js';
10
- *
10
+ *
11
11
  * const payvia = PayVia('YOUR_API_KEY');
12
- *
12
+ *
13
13
  * // Check if user paid
14
14
  * const user = await payvia.getUser();
15
15
  * if (user.paid) {
16
16
  * // Enable premium features
17
17
  * }
18
- *
18
+ *
19
19
  * // Open payment page
20
20
  * payvia.openPaymentPage();
21
21
  * ```
22
22
  */
23
23
 
24
+ // ============ Type Definitions (JSDoc) ============
25
+ // These @typedef blocks power the generated TypeScript declarations (payvia.d.ts).
26
+
27
+ /**
28
+ * @typedef {Object} Tier
29
+ * @property {string} id
30
+ * @property {string} name
31
+ * @property {number} level - 0=Free, 1=Pro, 2=Super
32
+ * @property {string[]} features
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} Identity
37
+ * @property {string} id - Unique user identifier (email or random pv_* ID)
38
+ * @property {string | null} email - User email (if known)
39
+ * @property {'google' | 'random'} source - How the identity was obtained
40
+ */
41
+
42
+ /**
43
+ * @typedef {Object} PayViaUser
44
+ * @property {string} id
45
+ * @property {string | null} email
46
+ * @property {'google' | 'random'} identitySource
47
+ * @property {boolean} paid - True if status is ACTIVE or TRIAL
48
+ * @property {'ACTIVE' | 'TRIAL' | 'INACTIVE'} status
49
+ * @property {Tier | null} tier
50
+ * @property {string[]} features - Shortcut to tier.features
51
+ * @property {string[]} planIds
52
+ * @property {boolean} isTrial
53
+ * @property {Date | null} [trialExpiresAt]
54
+ * @property {number | null} [daysRemaining]
55
+ * @property {Date | null} [canceledAt]
56
+ * @property {Date | null} [currentPeriodEnd]
57
+ * @property {boolean} [isCanceled]
58
+ * @property {boolean} [cancelGraceActive]
59
+ * @property {boolean} fromCache - True if data came from cache
60
+ * @property {number | null} [checkedAt] - Unix timestamp in ms
61
+ * @property {number | null} [ttl] - Cache TTL in ms
62
+ * @property {string | null} [signature] - HMAC anti-tamper signature
63
+ * @property {string} [error] - Set only when a network error occurred and no valid cache existed
64
+ */
65
+
66
+ /**
67
+ * @typedef {Object} TrialStartResult
68
+ * @property {string} subscriptionId
69
+ * @property {string} status
70
+ * @property {string} planId
71
+ * @property {string} planName
72
+ * @property {Date} trialExpiresAt
73
+ * @property {number} daysRemaining
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} TrialStatus
78
+ * @property {string} status
79
+ * @property {Date | null} trialExpiresAt
80
+ * @property {number | null} daysRemaining
81
+ * @property {boolean} canConvert
82
+ * @property {string[]} planIds
83
+ */
84
+
85
+ /**
86
+ * @typedef {Object} Plan
87
+ * @property {string} id
88
+ * @property {string} name
89
+ * @property {string} [description]
90
+ * @property {number} [price]
91
+ * @property {string} [currency]
92
+ * @property {string} [interval]
93
+ * @property {string} [tierId]
94
+ */
95
+
96
+ /**
97
+ * @typedef {Object} OpenPaymentPageOptions
98
+ * @property {'pricing' | 'hosted' | 'direct'} [mode] - Checkout mode (default: 'pricing')
99
+ * @property {string} [planId] - Required for 'hosted' and 'direct' modes
100
+ * @property {string} [email] - Customer email (optional; PayPal collects it for anonymous users)
101
+ * @property {string} [successUrl] - Redirect URL after successful payment ('direct' mode only)
102
+ * @property {string} [cancelUrl] - Redirect URL if user cancels ('direct' mode only)
103
+ */
104
+
105
+ /**
106
+ * @typedef {Object} OpenPaymentPageResult
107
+ * @property {string | null} [mode] - 'pricing' | 'hosted' for those modes; null/'iframe'/'redirect' for 'direct' mode (reflects PayVia backend response shape)
108
+ * @property {string} [pricingUrl]
109
+ * @property {string} [checkoutUrl]
110
+ * @property {string} [sessionId]
111
+ * @property {string} [provider] - 'PayPal' | 'Tranzila'
112
+ */
113
+
114
+ /**
115
+ * @typedef {Object} CancelSubscriptionOptions
116
+ * @property {string} [planId] - Specific plan to cancel
117
+ * @property {string} [reason] - Cancellation reason
118
+ */
119
+
120
+ /**
121
+ * @typedef {Object} CancelSubscriptionResult
122
+ * @property {boolean} success
123
+ * @property {string} message
124
+ * @property {string} [canceledPlanId]
125
+ */
126
+
127
+ /**
128
+ * @typedef {Object} ResetLicenseResult
129
+ * @property {string} message
130
+ */
131
+
132
+ /**
133
+ * @callback OnPaidCallback
134
+ * @param {PayViaUser} user
135
+ * @returns {void}
136
+ */
137
+
138
+ /**
139
+ * @typedef {Object} PayViaInstance
140
+ * @property {(options?: { forceRefresh?: boolean }) => Promise<PayViaUser>} getUser
141
+ * @property {() => Promise<PayViaUser>} refresh
142
+ * @property {() => Promise<void>} refreshLicenseCache
143
+ * @property {(featureName: string) => Promise<boolean>} hasFeature
144
+ * @property {(requiredLevel: number) => Promise<boolean>} hasTierLevel
145
+ * @property {() => Promise<Tier | null>} getTier
146
+ * @property {() => Promise<TrialStartResult | null>} startTrial
147
+ * @property {() => Promise<TrialStatus>} getTrialStatus
148
+ * @property {() => Promise<boolean>} isFirstRun
149
+ * @property {() => Promise<void>} markFirstRunDone
150
+ * @property {() => Promise<boolean>} needsEmailForPayment
151
+ * @property {() => Promise<Identity>} getIdentity
152
+ * @property {(options?: OpenPaymentPageOptions) => Promise<OpenPaymentPageResult>} openPaymentPage
153
+ * @property {(callback: OnPaidCallback) => (() => void)} onPaid
154
+ * @property {() => Promise<Plan[]>} getPlans
155
+ * @property {() => Promise<ResetLicenseResult>} resetLicense
156
+ * @property {(options?: CancelSubscriptionOptions) => Promise<CancelSubscriptionResult>} cancelSubscription
157
+ */
158
+
159
+ /**
160
+ * Create a PayVia SDK instance.
161
+ * @param {string} apiKey - API key from the PayVia dashboard
162
+ * @returns {PayViaInstance} PayVia SDK instance
163
+ */
24
164
  function PayVia(apiKey) {
25
165
  if (!apiKey) {
26
166
  throw new Error('PayVia: API key is required');
@@ -32,8 +172,11 @@ function PayVia(apiKey) {
32
172
  const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
33
173
  const GRACE_PERIOD_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
34
174
 
35
- const instance = {};
175
+ /** @type {PayViaInstance} */
176
+ const instance = /** @type {PayViaInstance} */ ({});
177
+ /** @type {PayViaUser | null} */
36
178
  let cachedUser = null;
179
+ /** @type {Promise<PayViaUser> | null} */
37
180
  let userPromise = null;
38
181
 
39
182
  // ============ License Cache Storage ============
@@ -233,10 +376,9 @@ function PayVia(apiKey) {
233
376
  }
234
377
 
235
378
  /**
236
- * Get user's payment status
379
+ * Get user's payment status.
237
380
  * Uses cache first, then server. Falls back to cache during network errors.
238
- * @param {Object} options - Options
239
- * @param {boolean} options.forceRefresh - Force fetch from server
381
+ * @param {{ forceRefresh?: boolean }} [options]
240
382
  * @returns {Promise<PayViaUser>} User object with payment status
241
383
  */
242
384
  instance.getUser = async function (options = {}) {
@@ -276,6 +418,8 @@ function PayVia(apiKey) {
276
418
  isTrial: response.isTrial || false,
277
419
  trialExpiresAt: response.trialExpiresAt || null,
278
420
  daysRemaining: response.daysRemaining || null,
421
+ canceledAt: response.canceledAt || null,
422
+ currentPeriodEnd: response.currentPeriodEnd || null,
279
423
  checkedAt: response.checkedAt || Date.now(),
280
424
  ttl: response.ttl || DEFAULT_TTL_MS,
281
425
  signature: response.signature || null,
@@ -322,6 +466,11 @@ function PayVia(apiKey) {
322
466
  * Build user object from cache data
323
467
  */
324
468
  function buildUserFromCache(identity, cache, fromCache) {
469
+ const canceledAt = cache.canceledAt ? new Date(cache.canceledAt) : null;
470
+ const currentPeriodEnd = cache.currentPeriodEnd ? new Date(cache.currentPeriodEnd) : null;
471
+ const isCanceled = !!canceledAt;
472
+ const cancelGraceActive = isCanceled && currentPeriodEnd && currentPeriodEnd > new Date();
473
+
325
474
  return {
326
475
  id: identity.id,
327
476
  email: identity.email,
@@ -334,6 +483,10 @@ function PayVia(apiKey) {
334
483
  isTrial: cache.isTrial || false,
335
484
  trialExpiresAt: cache.trialExpiresAt ? new Date(cache.trialExpiresAt) : null,
336
485
  daysRemaining: cache.daysRemaining || null,
486
+ canceledAt: canceledAt,
487
+ currentPeriodEnd: currentPeriodEnd,
488
+ isCanceled: isCanceled,
489
+ cancelGraceActive: cancelGraceActive,
337
490
  fromCache: fromCache,
338
491
  checkedAt: cache.checkedAt || null,
339
492
  ttl: cache.ttl || null,
@@ -384,8 +537,8 @@ function PayVia(apiKey) {
384
537
  };
385
538
 
386
539
  /**
387
- * Get the user's current tier info
388
- * @returns {Promise<{id: string, name: string, level: number, features: string[]}|null>}
540
+ * Get the user's current tier info.
541
+ * @returns {Promise<Tier | null>}
389
542
  */
390
543
  instance.getTier = async function () {
391
544
  const user = await instance.getUser();
@@ -393,10 +546,10 @@ function PayVia(apiKey) {
393
546
  };
394
547
 
395
548
  /**
396
- * Start a trial for the current user
397
- * Call this when user first installs/uses the extension
398
- * Idempotent: if user already has a trial/active subscription, returns existing info
399
- * @returns {Promise<{subscriptionId: string, status: string, planId: string, planName: string, trialExpiresAt: Date, daysRemaining: number} | null>}
549
+ * Start a trial for the current user.
550
+ * Call this when user first installs/uses the extension.
551
+ * Idempotent: if user already has a trial/active subscription, returns existing info.
552
+ * @returns {Promise<TrialStartResult | null>}
400
553
  */
401
554
  instance.startTrial = async function () {
402
555
  try {
@@ -427,8 +580,8 @@ function PayVia(apiKey) {
427
580
  };
428
581
 
429
582
  /**
430
- * Get trial status for the current user
431
- * @returns {Promise<{status: string, trialExpiresAt: Date | null, daysRemaining: number | null, canConvert: boolean, planIds: string[]}>}
583
+ * Get trial status for the current user.
584
+ * @returns {Promise<TrialStatus>}
432
585
  */
433
586
  instance.getTrialStatus = async function () {
434
587
  try {
@@ -475,6 +628,7 @@ function PayVia(apiKey) {
475
628
 
476
629
  /**
477
630
  * Mark first run as complete
631
+ * @returns {Promise<void>}
478
632
  */
479
633
  instance.markFirstRunDone = async function () {
480
634
  return new Promise((resolve) => {
@@ -497,24 +651,17 @@ function PayVia(apiKey) {
497
651
  };
498
652
 
499
653
  /**
500
- * Get the current user identity info
501
- * @returns {Promise<{id: string, email: string | null, source: 'google' | 'random'}>}
654
+ * Get the current user identity info.
655
+ * @returns {Promise<Identity>}
502
656
  */
503
657
  instance.getIdentity = async function () {
504
658
  return getUserIdentity();
505
659
  };
506
660
 
507
661
  /**
508
- * Open payment page for the user
509
- * @param {Object} options - Payment options
510
- * @param {string} options.planId - Plan ID to purchase (required for direct/hosted modes)
511
- * @param {string} options.email - Customer email
512
- * @param {'pricing'|'hosted'|'direct'} options.mode - Checkout mode:
513
- * - 'pricing': Shows all plans for user to choose (default, most secure)
514
- * - 'hosted': Goes directly to checkout for specific plan
515
- * - 'direct': Bypasses PayVia, goes straight to PayPal
516
- * @param {string} options.successUrl - URL to redirect after successful payment (direct mode only)
517
- * @param {string} options.cancelUrl - URL to redirect if user cancels (direct mode only)
662
+ * Open payment page for the user.
663
+ * @param {OpenPaymentPageOptions} [options] - Payment options
664
+ * @returns {Promise<OpenPaymentPageResult>}
518
665
  */
519
666
  instance.openPaymentPage = async function (options = {}) {
520
667
  const identity = await getUserIdentity();
@@ -525,15 +672,12 @@ function PayVia(apiKey) {
525
672
 
526
673
  if (mode === 'pricing') {
527
674
  // Pricing mode: Get secure token, show all plans
528
- if (!customerEmail) {
529
- throw new Error('Email is required for payment. Please provide an email address.');
530
- }
531
-
675
+ // Email is optional — PayPal will collect it during checkout for anon users
532
676
  const tokenResponse = await apiRequest('/api/v1/checkout/token', {
533
677
  method: 'POST',
534
678
  body: JSON.stringify({
535
679
  customerId: identity.id,
536
- customerEmail: customerEmail,
680
+ customerEmail: customerEmail || undefined,
537
681
  mode: 'pricing',
538
682
  }),
539
683
  });
@@ -552,15 +696,12 @@ function PayVia(apiKey) {
552
696
  if (!options.planId) {
553
697
  throw new Error('planId is required for hosted mode');
554
698
  }
555
- if (!customerEmail) {
556
- throw new Error('Email is required for payment. Please provide an email address.');
557
- }
558
-
699
+ // Email is optional — PayPal will collect it during checkout for anon users
559
700
  const tokenResponse = await apiRequest('/api/v1/checkout/token', {
560
701
  method: 'POST',
561
702
  body: JSON.stringify({
562
703
  customerId: identity.id,
563
- customerEmail: customerEmail,
704
+ customerEmail: customerEmail || undefined,
564
705
  planId: options.planId,
565
706
  mode: 'checkout',
566
707
  }),
@@ -576,7 +717,7 @@ function PayVia(apiKey) {
576
717
 
577
718
  return { mode: 'hosted', checkoutUrl };
578
719
  } else {
579
- // Direct mode: Call API and redirect straight to PayPal (original behavior)
720
+ // Direct mode: Call API and redirect to payment provider
580
721
  if (!options.planId) {
581
722
  throw new Error('planId is required for direct mode');
582
723
  }
@@ -587,13 +728,15 @@ function PayVia(apiKey) {
587
728
  body: JSON.stringify({
588
729
  planId: options.planId,
589
730
  customerId: identity.id,
590
- customerEmail: customerEmail || '',
731
+ customerEmail: customerEmail || undefined,
591
732
  successUrl: options.successUrl || 'https://payvia.site/success',
592
733
  cancelUrl: options.cancelUrl || 'https://payvia.site/cancel',
593
734
  }),
594
735
  });
595
736
 
596
- // Open PayPal checkout in new tab
737
+ // Open checkout URL in new tab
738
+ // For Tranzila iframe mode, the URL is the iframe source — opening it
739
+ // directly in a new tab works fine (Tranzila renders as a full page too)
597
740
  if (response.checkoutUrl) {
598
741
  if (typeof chrome !== 'undefined' && chrome.tabs) {
599
742
  chrome.tabs.create({ url: response.checkoutUrl });
@@ -602,6 +745,7 @@ function PayVia(apiKey) {
602
745
  }
603
746
  }
604
747
 
748
+ // response includes: checkoutUrl, sessionId, provider ("PayPal"|"Tranzila"), mode (null|"iframe"|"redirect")
605
749
  return response;
606
750
  } catch (error) {
607
751
  console.error('PayVia: Failed to open payment page', error);
@@ -611,8 +755,9 @@ function PayVia(apiKey) {
611
755
  };
612
756
 
613
757
  /**
614
- * Listen for payment status changes
615
- * @param {Function} callback - Called when payment status changes
758
+ * Listen for payment status changes (polls every 5 seconds).
759
+ * @param {OnPaidCallback} callback - Called when payment status changes
760
+ * @returns {() => void} Unsubscribe function - call to stop listening
616
761
  */
617
762
  instance.onPaid = function (callback) {
618
763
  // Poll for status changes every 5 seconds when tab is visible
@@ -653,7 +798,7 @@ function PayVia(apiKey) {
653
798
  /**
654
799
  * Reset the user's license (delete all subscriptions).
655
800
  * This is for demo/testing purposes only.
656
- * @returns {Promise<{message: string}>}
801
+ * @returns {Promise<ResetLicenseResult>}
657
802
  */
658
803
  instance.resetLicense = async function () {
659
804
  const identity = await getUserIdentity();
@@ -666,11 +811,9 @@ function PayVia(apiKey) {
666
811
  };
667
812
 
668
813
  /**
669
- * Cancel a subscription for the current user
670
- * @param {Object} options - Cancel options
671
- * @param {string} options.planId - Specific plan to cancel (optional)
672
- * @param {string} options.reason - Cancellation reason (optional)
673
- * @returns {Promise<{success: boolean, message: string, canceledPlanId: string}>}
814
+ * Cancel a subscription for the current user.
815
+ * @param {CancelSubscriptionOptions} [options]
816
+ * @returns {Promise<CancelSubscriptionResult>}
674
817
  */
675
818
  instance.cancelSubscription = async function (options = {}) {
676
819
  const identity = await getUserIdentity();