@qazuor/qzpay-core 1.0.0 → 1.1.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 +378 -19
- package/dist/index.cjs +1800 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1861 -201
- package/dist/index.d.ts +1861 -201
- package/dist/index.js +1769 -137
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ pnpm add @qazuor/qzpay-core
|
|
|
16
16
|
- **Metrics Service**: MRR, churn, and revenue calculations
|
|
17
17
|
- **Health Service**: System health monitoring
|
|
18
18
|
- **Logger**: Structured logging with customizable providers
|
|
19
|
+
- **Input Validation**: Zod-based validation for all create operations
|
|
20
|
+
- **Promo Code Validation**: Full validation with plan applicability and usage limits
|
|
19
21
|
- **Utilities**: Date, money, validation, and hash helpers
|
|
20
22
|
- **Runtime Agnostic**: Works in Node.js, Bun, Deno, and Edge runtimes (no direct `process.env` access)
|
|
21
23
|
|
|
@@ -78,8 +80,8 @@ await billing.subscriptions.cancel('sub_123');
|
|
|
78
80
|
### Payment Operations
|
|
79
81
|
|
|
80
82
|
```typescript
|
|
81
|
-
//
|
|
82
|
-
const payment = await billing.payments.
|
|
83
|
+
// Process a payment
|
|
84
|
+
const payment = await billing.payments.process({
|
|
83
85
|
customerId: 'cus_123',
|
|
84
86
|
amount: 2999, // $29.99 in cents
|
|
85
87
|
currency: 'USD',
|
|
@@ -89,8 +91,26 @@ const payment = await billing.payments.create({
|
|
|
89
91
|
// Retrieve payment
|
|
90
92
|
const status = await billing.payments.get('pay_123');
|
|
91
93
|
|
|
94
|
+
// Get payments by customer
|
|
95
|
+
const payments = await billing.payments.getByCustomerId('cus_123');
|
|
96
|
+
|
|
97
|
+
// Record an external payment (already processed by provider)
|
|
98
|
+
const recorded = await billing.payments.record({
|
|
99
|
+
id: 'pay_external_123',
|
|
100
|
+
customerId: 'cus_123',
|
|
101
|
+
amount: 2999,
|
|
102
|
+
currency: 'USD',
|
|
103
|
+
status: 'succeeded',
|
|
104
|
+
providerPaymentId: 'pi_xxx',
|
|
105
|
+
provider: 'stripe'
|
|
106
|
+
});
|
|
107
|
+
|
|
92
108
|
// Refund payment
|
|
93
|
-
await billing.payments.refund(
|
|
109
|
+
await billing.payments.refund({
|
|
110
|
+
paymentId: 'pay_123',
|
|
111
|
+
amount: 1500, // Optional: partial refund
|
|
112
|
+
reason: 'requested_by_customer'
|
|
113
|
+
});
|
|
94
114
|
```
|
|
95
115
|
|
|
96
116
|
### Events
|
|
@@ -111,27 +131,44 @@ billing.on('payment.succeeded', (event) => {
|
|
|
111
131
|
|
|
112
132
|
### Metrics
|
|
113
133
|
|
|
114
|
-
|
|
115
|
-
import { createMetricsService } from '@qazuor/qzpay-core';
|
|
116
|
-
|
|
117
|
-
const metrics = createMetricsService({ storage });
|
|
134
|
+
The `billing.metrics` service provides business intelligence and analytics:
|
|
118
135
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
// Get Monthly Recurring Revenue
|
|
138
|
+
const mrr = await billing.metrics.getMrr({ currency: 'USD' });
|
|
139
|
+
console.log('Current MRR:', mrr.currentMrr);
|
|
140
|
+
console.log('Previous MRR:', mrr.previousMrr);
|
|
141
|
+
console.log('Net New MRR:', mrr.netNewMrr);
|
|
142
|
+
|
|
143
|
+
// Get subscription metrics by status
|
|
144
|
+
const subMetrics = await billing.metrics.getSubscriptionMetrics();
|
|
145
|
+
console.log('Active:', subMetrics.active);
|
|
146
|
+
console.log('Trialing:', subMetrics.trialing);
|
|
147
|
+
console.log('Canceled:', subMetrics.canceled);
|
|
148
|
+
console.log('Past Due:', subMetrics.pastDue);
|
|
149
|
+
|
|
150
|
+
// Get revenue metrics for a period
|
|
151
|
+
const revenue = await billing.metrics.getRevenueMetrics({
|
|
152
|
+
startDate: new Date('2024-01-01'),
|
|
153
|
+
endDate: new Date('2024-12-31'),
|
|
154
|
+
currency: 'USD'
|
|
155
|
+
});
|
|
156
|
+
console.log('Total Revenue:', revenue.totalRevenue);
|
|
157
|
+
console.log('Refunds:', revenue.totalRefunds);
|
|
122
158
|
|
|
123
|
-
// Get churn
|
|
124
|
-
const churn = await metrics.
|
|
125
|
-
|
|
126
|
-
|
|
159
|
+
// Get churn metrics
|
|
160
|
+
const churn = await billing.metrics.getChurnMetrics({
|
|
161
|
+
startDate: new Date('2024-01-01'),
|
|
162
|
+
endDate: new Date('2024-12-31')
|
|
127
163
|
});
|
|
164
|
+
console.log('Churn Rate:', churn.churnRate);
|
|
165
|
+
console.log('Churned MRR:', churn.churnedMrr);
|
|
128
166
|
|
|
129
|
-
// Get
|
|
130
|
-
const
|
|
131
|
-
from: new Date('2024-01-01'),
|
|
132
|
-
to: new Date('2024-12-31'),
|
|
167
|
+
// Get all dashboard metrics aggregated
|
|
168
|
+
const dashboard = await billing.metrics.getDashboard({
|
|
133
169
|
currency: 'USD'
|
|
134
170
|
});
|
|
171
|
+
console.log('Dashboard:', dashboard);
|
|
135
172
|
```
|
|
136
173
|
|
|
137
174
|
### Health Checks
|
|
@@ -201,11 +238,266 @@ const billing = new QZPayBilling({
|
|
|
201
238
|
| `billing.payments` | Payment processing |
|
|
202
239
|
| `billing.invoices` | Invoice management |
|
|
203
240
|
| `billing.plans` | Plan configuration |
|
|
204
|
-
| `billing.prices` | Price management |
|
|
205
241
|
| `billing.promoCodes` | Promotional codes |
|
|
206
242
|
| `billing.entitlements` | Feature entitlements |
|
|
207
243
|
| `billing.limits` | Usage limits |
|
|
208
244
|
| `billing.addons` | Add-on management |
|
|
245
|
+
| `billing.paymentMethods` | Payment method management |
|
|
246
|
+
| `billing.metrics` | Business metrics and analytics |
|
|
247
|
+
|
|
248
|
+
### Entitlements Service
|
|
249
|
+
|
|
250
|
+
Manage feature access based on subscriptions:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Check if customer has an entitlement
|
|
254
|
+
const hasAccess = await billing.entitlements.check('cus_123', 'advanced_analytics');
|
|
255
|
+
if (hasAccess) {
|
|
256
|
+
// Show advanced analytics
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Get all entitlements for a customer
|
|
260
|
+
const entitlements = await billing.entitlements.getByCustomerId('cus_123');
|
|
261
|
+
|
|
262
|
+
// Grant an entitlement manually
|
|
263
|
+
await billing.entitlements.grant('cus_123', 'beta_features', 'manual', 'admin_grant');
|
|
264
|
+
|
|
265
|
+
// Revoke an entitlement
|
|
266
|
+
await billing.entitlements.revoke('cus_123', 'beta_features');
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Limits Service
|
|
270
|
+
|
|
271
|
+
Track and enforce usage limits:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// Check if customer is within limit
|
|
275
|
+
const result = await billing.limits.check('cus_123', 'api_calls');
|
|
276
|
+
console.log('Allowed:', result.allowed);
|
|
277
|
+
console.log('Current:', result.currentValue);
|
|
278
|
+
console.log('Max:', result.maxValue);
|
|
279
|
+
console.log('Remaining:', result.remaining);
|
|
280
|
+
|
|
281
|
+
// Get all limits for a customer
|
|
282
|
+
const limits = await billing.limits.getByCustomerId('cus_123');
|
|
283
|
+
|
|
284
|
+
// Increment usage
|
|
285
|
+
await billing.limits.increment('cus_123', 'api_calls', 1);
|
|
286
|
+
|
|
287
|
+
// Set a limit value
|
|
288
|
+
await billing.limits.set('cus_123', 'api_calls', 10000);
|
|
289
|
+
|
|
290
|
+
// Record usage (for audit/history)
|
|
291
|
+
await billing.limits.recordUsage('cus_123', 'api_calls', 5, 'increment');
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Add-ons Service
|
|
295
|
+
|
|
296
|
+
Manage subscription add-ons:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// Create an add-on definition
|
|
300
|
+
const addon = await billing.addons.create({
|
|
301
|
+
name: 'Extra Storage',
|
|
302
|
+
unitAmount: 500, // $5.00 in cents
|
|
303
|
+
currency: 'USD',
|
|
304
|
+
billingInterval: 'month',
|
|
305
|
+
compatiblePlanIds: ['plan_pro', 'plan_enterprise']
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Get add-ons compatible with a plan
|
|
309
|
+
const addons = await billing.addons.getByPlanId('plan_pro');
|
|
310
|
+
|
|
311
|
+
// Add an add-on to a subscription
|
|
312
|
+
const result = await billing.addons.addToSubscription({
|
|
313
|
+
subscriptionId: 'sub_123',
|
|
314
|
+
addOnId: 'addon_extra_storage',
|
|
315
|
+
quantity: 2
|
|
316
|
+
});
|
|
317
|
+
console.log('Proration amount:', result.prorationAmount);
|
|
318
|
+
|
|
319
|
+
// Get add-ons attached to a subscription
|
|
320
|
+
const subscriptionAddons = await billing.addons.getBySubscriptionId('sub_123');
|
|
321
|
+
|
|
322
|
+
// Update add-on quantity
|
|
323
|
+
await billing.addons.updateSubscriptionAddOn('sub_123', 'addon_extra_storage', {
|
|
324
|
+
quantity: 5
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Remove add-on from subscription
|
|
328
|
+
await billing.addons.removeFromSubscription('sub_123', 'addon_extra_storage');
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Payment Methods Service
|
|
332
|
+
|
|
333
|
+
Manage customer payment methods:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// Create a payment method
|
|
337
|
+
const paymentMethod = await billing.paymentMethods.create({
|
|
338
|
+
customerId: 'cus_123',
|
|
339
|
+
type: 'card',
|
|
340
|
+
providerPaymentMethodId: 'pm_xxx',
|
|
341
|
+
provider: 'stripe',
|
|
342
|
+
card: {
|
|
343
|
+
brand: 'visa',
|
|
344
|
+
last4: '4242',
|
|
345
|
+
expMonth: 12,
|
|
346
|
+
expYear: 2025
|
|
347
|
+
},
|
|
348
|
+
setAsDefault: true
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Get payment methods for a customer
|
|
352
|
+
const methods = await billing.paymentMethods.getByCustomerId('cus_123');
|
|
353
|
+
|
|
354
|
+
// Get the default payment method
|
|
355
|
+
const defaultMethod = await billing.paymentMethods.getDefault('cus_123');
|
|
356
|
+
|
|
357
|
+
// Set a payment method as default
|
|
358
|
+
await billing.paymentMethods.setDefault('cus_123', 'pm_456');
|
|
359
|
+
|
|
360
|
+
// Update a payment method
|
|
361
|
+
await billing.paymentMethods.update('pm_123', {
|
|
362
|
+
card: { expMonth: 1, expYear: 2026 }
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Delete a payment method
|
|
366
|
+
await billing.paymentMethods.delete('pm_123');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Saved Card Service
|
|
370
|
+
|
|
371
|
+
Unified interface for saving and managing payment cards across providers.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { createSavedCardService } from '@qazuor/qzpay-stripe'; // or '@qazuor/qzpay-mercadopago'
|
|
375
|
+
|
|
376
|
+
const cardService = createSavedCardService({
|
|
377
|
+
provider: 'stripe',
|
|
378
|
+
stripeSecretKey: 'sk_xxx',
|
|
379
|
+
getProviderCustomerId: async (customerId) => {
|
|
380
|
+
const customer = await db.customers.findById(customerId);
|
|
381
|
+
return customer.stripeCustomerId;
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Save a card
|
|
386
|
+
const card = await cardService.save({
|
|
387
|
+
customerId: 'local_cus_123',
|
|
388
|
+
paymentMethodId: 'pm_xxx', // From Stripe.js
|
|
389
|
+
setAsDefault: true,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// List all cards
|
|
393
|
+
const cards = await cardService.list('local_cus_123');
|
|
394
|
+
|
|
395
|
+
// Set card as default (Stripe only)
|
|
396
|
+
await cardService.setDefault('local_cus_123', 'pm_xxx');
|
|
397
|
+
|
|
398
|
+
// Remove a card
|
|
399
|
+
await cardService.remove('local_cus_123', 'pm_xxx');
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Provider Differences:**
|
|
403
|
+
|
|
404
|
+
| Feature | Stripe | MercadoPago |
|
|
405
|
+
|---------|--------|-------------|
|
|
406
|
+
| Save card | `paymentMethodId` | `token` |
|
|
407
|
+
| List cards | ✅ | ✅ |
|
|
408
|
+
| Remove card | ✅ | ✅ |
|
|
409
|
+
| Set default | ✅ Native | ❌ Track in your DB |
|
|
410
|
+
|
|
411
|
+
**Note:** MercadoPago doesn't support default payment methods natively. Your application must track the default card ID in its database.
|
|
412
|
+
|
|
413
|
+
### Subscription Lifecycle Service
|
|
414
|
+
|
|
415
|
+
Automates subscription renewals, trial conversions, and payment retries.
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { createSubscriptionLifecycle } from '@qazuor/qzpay-core';
|
|
419
|
+
|
|
420
|
+
const lifecycle = createSubscriptionLifecycle(billing, storage, {
|
|
421
|
+
gracePeriodDays: 7,
|
|
422
|
+
retryIntervals: [1, 3, 5], // Retry after 1, 3, and 5 days
|
|
423
|
+
trialConversionDays: 0, // Convert immediately when trial ends
|
|
424
|
+
|
|
425
|
+
// Process payment callback
|
|
426
|
+
processPayment: async (input) => {
|
|
427
|
+
const result = await stripe.paymentIntents.create({
|
|
428
|
+
amount: input.amount,
|
|
429
|
+
currency: input.currency,
|
|
430
|
+
customer: await getStripeCustomerId(input.customerId),
|
|
431
|
+
payment_method: input.paymentMethodId,
|
|
432
|
+
confirm: true,
|
|
433
|
+
off_session: true,
|
|
434
|
+
});
|
|
435
|
+
return {
|
|
436
|
+
success: result.status === 'succeeded',
|
|
437
|
+
paymentId: result.id,
|
|
438
|
+
error: result.last_payment_error?.message,
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// Get default payment method callback
|
|
443
|
+
getDefaultPaymentMethod: async (customerId) => {
|
|
444
|
+
const pm = await db.paymentMethods.findDefault(customerId);
|
|
445
|
+
return pm ? {
|
|
446
|
+
id: pm.id,
|
|
447
|
+
providerPaymentMethodId: pm.stripePaymentMethodId
|
|
448
|
+
} : null;
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
// Optional: event callback for logging/notifications
|
|
452
|
+
onEvent: async (event) => {
|
|
453
|
+
console.log(`[${event.type}] Subscription: ${event.subscriptionId}`);
|
|
454
|
+
|
|
455
|
+
// Send notifications
|
|
456
|
+
if (event.type === 'subscription.renewal_failed') {
|
|
457
|
+
await sendEmail({
|
|
458
|
+
to: customer.email,
|
|
459
|
+
subject: 'Payment Failed',
|
|
460
|
+
body: 'Your subscription payment failed. Please update your payment method.'
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Run from a cron job
|
|
467
|
+
const results = await lifecycle.processAll();
|
|
468
|
+
console.log('Renewals:', results.renewals);
|
|
469
|
+
console.log('Trial conversions:', results.trialConversions);
|
|
470
|
+
console.log('Retries:', results.retries);
|
|
471
|
+
console.log('Cancellations:', results.cancellations);
|
|
472
|
+
|
|
473
|
+
// Or run individual operations
|
|
474
|
+
await lifecycle.processRenewals();
|
|
475
|
+
await lifecycle.processTrialConversions();
|
|
476
|
+
await lifecycle.processRetries();
|
|
477
|
+
await lifecycle.processCancellations();
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Lifecycle Events:**
|
|
481
|
+
|
|
482
|
+
- `subscription.renewed` - Subscription successfully renewed
|
|
483
|
+
- `subscription.renewal_failed` - Renewal payment failed
|
|
484
|
+
- `subscription.trial_converted` - Trial converted to paid
|
|
485
|
+
- `subscription.trial_conversion_failed` - Trial conversion failed
|
|
486
|
+
- `subscription.entered_grace_period` - Entered grace period after failed payment
|
|
487
|
+
- `subscription.retry_scheduled` - Payment retry scheduled
|
|
488
|
+
- `subscription.retry_succeeded` - Retry payment succeeded
|
|
489
|
+
- `subscription.retry_failed` - All retries exhausted
|
|
490
|
+
- `subscription.canceled_nonpayment` - Canceled due to non-payment
|
|
491
|
+
|
|
492
|
+
**Cron Setup Example:**
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// Run every hour
|
|
496
|
+
cron.schedule('0 * * * *', async () => {
|
|
497
|
+
const results = await lifecycle.processAll();
|
|
498
|
+
console.log(`Processed ${results.renewals.processed} renewals`);
|
|
499
|
+
});
|
|
500
|
+
```
|
|
209
501
|
|
|
210
502
|
## Utility Functions
|
|
211
503
|
|
|
@@ -240,6 +532,73 @@ isValidAmount(100); // true
|
|
|
240
532
|
isValidAmount(-50); // false
|
|
241
533
|
```
|
|
242
534
|
|
|
535
|
+
### Input Validation
|
|
536
|
+
|
|
537
|
+
All create operations now include comprehensive Zod validation:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// Customer creation with validation
|
|
541
|
+
try {
|
|
542
|
+
const customer = await billing.customers.create({
|
|
543
|
+
email: 'invalid-email', // Will throw validation error
|
|
544
|
+
name: 'John Doe'
|
|
545
|
+
});
|
|
546
|
+
} catch (error) {
|
|
547
|
+
// error.code === 'VALIDATION_ERROR'
|
|
548
|
+
// error.details contains validation failure info
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Payment with validation
|
|
552
|
+
await billing.payments.process({
|
|
553
|
+
customerId: 'cus_123',
|
|
554
|
+
amount: -100, // Will throw validation error (negative amount)
|
|
555
|
+
currency: 'INVALID' // Will throw validation error (invalid currency)
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Invoice creation with validation
|
|
559
|
+
await billing.invoices.create({
|
|
560
|
+
customerId: 'cus_123',
|
|
561
|
+
items: [] // Will throw validation error (empty items array)
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Promo Code Validation
|
|
566
|
+
|
|
567
|
+
Promo codes are validated against multiple criteria:
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// The system automatically validates:
|
|
571
|
+
// 1. Plan applicability
|
|
572
|
+
const promoCode = await billing.promoCodes.create({
|
|
573
|
+
code: 'SUMMER2024',
|
|
574
|
+
discountType: 'percentage',
|
|
575
|
+
discountValue: 20,
|
|
576
|
+
applicablePlans: ['plan_premium', 'plan_enterprise'] // Only works for these plans
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// 2. Per-customer usage limits
|
|
580
|
+
await billing.promoCodes.validate({
|
|
581
|
+
code: 'SUMMER2024',
|
|
582
|
+
customerId: 'cus_123',
|
|
583
|
+
planId: 'plan_basic' // Will fail if not in applicablePlans
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// 3. Date ranges
|
|
587
|
+
await billing.promoCodes.create({
|
|
588
|
+
code: 'NEWYEAR2024',
|
|
589
|
+
validFrom: new Date('2024-01-01'),
|
|
590
|
+
validTo: new Date('2024-01-31') // Only valid in January
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// 4. Max uses and active status
|
|
594
|
+
await billing.promoCodes.create({
|
|
595
|
+
code: 'LIMITED',
|
|
596
|
+
maxUses: 100, // Can only be used 100 times total
|
|
597
|
+
maxUsesPerCustomer: 1, // Each customer can use it once
|
|
598
|
+
active: true // Must be active
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
243
602
|
## Types
|
|
244
603
|
|
|
245
604
|
### Core Types
|