@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.
- package/README.md +19 -1
- package/package.json +18 -4
- package/payvia.d.ts +313 -0
- 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.
|
|
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
|
-
".":
|
|
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
|
-
|
|
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 {
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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 {
|
|
510
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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<
|
|
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 {
|
|
671
|
-
* @
|
|
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();
|