@qazuor/qzpay-core 1.0.0 → 1.0.1

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 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
 
@@ -240,6 +242,73 @@ isValidAmount(100); // true
240
242
  isValidAmount(-50); // false
241
243
  ```
242
244
 
245
+ ### Input Validation
246
+
247
+ All create operations now include comprehensive Zod validation:
248
+
249
+ ```typescript
250
+ // Customer creation with validation
251
+ try {
252
+ const customer = await billing.customers.create({
253
+ email: 'invalid-email', // Will throw validation error
254
+ name: 'John Doe'
255
+ });
256
+ } catch (error) {
257
+ // error.code === 'VALIDATION_ERROR'
258
+ // error.details contains validation failure info
259
+ }
260
+
261
+ // Payment with validation
262
+ await billing.payments.process({
263
+ customerId: 'cus_123',
264
+ amount: -100, // Will throw validation error (negative amount)
265
+ currency: 'INVALID' // Will throw validation error (invalid currency)
266
+ });
267
+
268
+ // Invoice creation with validation
269
+ await billing.invoices.create({
270
+ customerId: 'cus_123',
271
+ items: [] // Will throw validation error (empty items array)
272
+ });
273
+ ```
274
+
275
+ ### Promo Code Validation
276
+
277
+ Promo codes are validated against multiple criteria:
278
+
279
+ ```typescript
280
+ // The system automatically validates:
281
+ // 1. Plan applicability
282
+ const promoCode = await billing.promoCodes.create({
283
+ code: 'SUMMER2024',
284
+ discountType: 'percentage',
285
+ discountValue: 20,
286
+ applicablePlans: ['plan_premium', 'plan_enterprise'] // Only works for these plans
287
+ });
288
+
289
+ // 2. Per-customer usage limits
290
+ await billing.promoCodes.validate({
291
+ code: 'SUMMER2024',
292
+ customerId: 'cus_123',
293
+ planId: 'plan_basic' // Will fail if not in applicablePlans
294
+ });
295
+
296
+ // 3. Date ranges
297
+ await billing.promoCodes.create({
298
+ code: 'NEWYEAR2024',
299
+ validFrom: new Date('2024-01-01'),
300
+ validTo: new Date('2024-01-31') // Only valid in January
301
+ });
302
+
303
+ // 4. Max uses and active status
304
+ await billing.promoCodes.create({
305
+ code: 'LIMITED',
306
+ maxUses: 100, // Can only be used 100 times total
307
+ maxUsesPerCustomer: 1, // Each customer can use it once
308
+ active: true // Must be active
309
+ });
310
+ ```
311
+
243
312
  ## Types
244
313
 
245
314
  ### Core Types
package/dist/index.cjs CHANGED
@@ -1280,6 +1280,108 @@ var noopLogger = {
1280
1280
  }
1281
1281
  };
1282
1282
 
1283
+ // src/utils/validation.utils.ts
1284
+ function qzpayIsValidEmail(email) {
1285
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1286
+ return emailRegex.test(email);
1287
+ }
1288
+ function qzpayIsValidCurrency(currency) {
1289
+ return QZPAY_CURRENCY_VALUES.includes(currency.toUpperCase());
1290
+ }
1291
+ function qzpayIsPositiveInteger(value) {
1292
+ return Number.isInteger(value) && value > 0;
1293
+ }
1294
+ function qzpayIsNonNegativeInteger(value) {
1295
+ return Number.isInteger(value) && value >= 0;
1296
+ }
1297
+ function qzpayIsValidPercentage(value) {
1298
+ return value >= 0 && value <= 100;
1299
+ }
1300
+ function qzpayIsValidUuid(value) {
1301
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1302
+ return uuidRegex.test(value);
1303
+ }
1304
+ function qzpayIsRequiredString(value) {
1305
+ return typeof value === "string" && value.trim().length > 0;
1306
+ }
1307
+ function qzpayValidateRequired(obj, requiredFields) {
1308
+ const errors = [];
1309
+ for (const field of requiredFields) {
1310
+ const value = obj[field];
1311
+ if (value === void 0 || value === null || value === "") {
1312
+ errors.push(`${String(field)} is required`);
1313
+ }
1314
+ }
1315
+ return {
1316
+ valid: errors.length === 0,
1317
+ errors
1318
+ };
1319
+ }
1320
+ function qzpayAssert(condition, message) {
1321
+ if (!condition) {
1322
+ throw new Error(message);
1323
+ }
1324
+ }
1325
+ function qzpayAssertDefined(value, name) {
1326
+ if (value === null || value === void 0) {
1327
+ throw new Error(`${name} is required`);
1328
+ }
1329
+ }
1330
+ function qzpayCreateValidator(obj) {
1331
+ return new QZPayValidator(obj);
1332
+ }
1333
+ var QZPayValidator = class {
1334
+ constructor(obj) {
1335
+ this.obj = obj;
1336
+ }
1337
+ errors = [];
1338
+ required(field, message) {
1339
+ const value = this.obj[field];
1340
+ if (value === void 0 || value === null || value === "") {
1341
+ this.errors.push(message ?? `${String(field)} is required`);
1342
+ }
1343
+ return this;
1344
+ }
1345
+ email(field, message) {
1346
+ const value = this.obj[field];
1347
+ if (typeof value === "string" && !qzpayIsValidEmail(value)) {
1348
+ this.errors.push(message ?? `${String(field)} must be a valid email`);
1349
+ }
1350
+ return this;
1351
+ }
1352
+ positiveInteger(field, message) {
1353
+ const value = this.obj[field];
1354
+ if (typeof value === "number" && !qzpayIsPositiveInteger(value)) {
1355
+ this.errors.push(message ?? `${String(field)} must be a positive integer`);
1356
+ }
1357
+ return this;
1358
+ }
1359
+ currency(field, message) {
1360
+ const value = this.obj[field];
1361
+ if (typeof value === "string" && !qzpayIsValidCurrency(value)) {
1362
+ this.errors.push(message ?? `${String(field)} must be a valid currency`);
1363
+ }
1364
+ return this;
1365
+ }
1366
+ custom(condition, message) {
1367
+ if (!condition) {
1368
+ this.errors.push(message);
1369
+ }
1370
+ return this;
1371
+ }
1372
+ validate() {
1373
+ return {
1374
+ valid: this.errors.length === 0,
1375
+ errors: [...this.errors]
1376
+ };
1377
+ }
1378
+ assertValid() {
1379
+ if (this.errors.length > 0) {
1380
+ throw new Error(`Validation failed: ${this.errors.join(", ")}`);
1381
+ }
1382
+ }
1383
+ };
1384
+
1283
1385
  // src/billing.ts
1284
1386
  var QZPayBillingImpl = class {
1285
1387
  storage;
@@ -1307,40 +1409,128 @@ var QZPayBillingImpl = class {
1307
1409
  this.logger.info("QZPayBilling initialized", { livemode: this.livemode });
1308
1410
  }
1309
1411
  get customers() {
1412
+ const storage = this.storage;
1413
+ const emitter = this.emitter;
1414
+ const paymentAdapter = this.paymentAdapter;
1310
1415
  return {
1311
1416
  create: async (input) => {
1312
- const customer = await this.storage.customers.create(input);
1313
- await this.emitter.emit("customer.created", customer);
1417
+ qzpayCreateValidator(input).required("email", "Email is required").email("email", "Invalid email format").required("externalId", "External ID is required").custom(!input.name || typeof input.name === "string" && input.name.trim().length > 0, "Name cannot be empty string").assertValid();
1418
+ const customer = await storage.customers.create(input);
1419
+ if (paymentAdapter) {
1420
+ try {
1421
+ const providerInput = {
1422
+ email: input.email,
1423
+ externalId: input.externalId
1424
+ };
1425
+ if (input.name !== void 0) providerInput.name = input.name;
1426
+ if (input.metadata) providerInput.metadata = input.metadata;
1427
+ const providerCustomerId = await paymentAdapter.customers.create(providerInput);
1428
+ const updated = await storage.customers.update(customer.id, {
1429
+ providerCustomerIds: {
1430
+ ...customer.providerCustomerIds,
1431
+ [paymentAdapter.provider]: providerCustomerId
1432
+ }
1433
+ });
1434
+ await emitter.emit("customer.created", updated ?? customer);
1435
+ return updated ?? customer;
1436
+ } catch (error) {
1437
+ this.logger.error("Failed to create customer in provider", {
1438
+ provider: paymentAdapter.provider,
1439
+ error: error instanceof Error ? error.message : String(error)
1440
+ });
1441
+ await emitter.emit("customer.created", customer);
1442
+ return customer;
1443
+ }
1444
+ }
1445
+ await emitter.emit("customer.created", customer);
1314
1446
  return customer;
1315
1447
  },
1316
- get: (id) => this.storage.customers.findById(id),
1317
- getByExternalId: (externalId) => this.storage.customers.findByExternalId(externalId),
1448
+ get: (id) => storage.customers.findById(id),
1449
+ getByExternalId: (externalId) => storage.customers.findByExternalId(externalId),
1318
1450
  update: async (id, input) => {
1319
- const customer = await this.storage.customers.update(id, input);
1451
+ const customer = await storage.customers.update(id, input);
1320
1452
  if (customer) {
1321
- await this.emitter.emit("customer.updated", customer);
1453
+ await emitter.emit("customer.updated", customer);
1322
1454
  }
1323
1455
  return customer;
1324
1456
  },
1325
1457
  delete: async (id) => {
1326
- await this.storage.customers.delete(id);
1327
- const customer = await this.storage.customers.findById(id);
1458
+ await storage.customers.delete(id);
1459
+ const customer = await storage.customers.findById(id);
1328
1460
  if (customer) {
1329
- await this.emitter.emit("customer.deleted", customer);
1461
+ await emitter.emit("customer.deleted", customer);
1330
1462
  }
1331
1463
  },
1332
- list: (options) => this.storage.customers.list(options),
1464
+ list: (options) => storage.customers.list(options),
1333
1465
  syncUser: async (input) => {
1334
- const existing = await this.storage.customers.findByExternalId(input.externalId ?? "");
1466
+ const existing = await storage.customers.findByExternalId(input.externalId ?? "");
1335
1467
  if (existing) {
1336
- const updated = await this.storage.customers.update(existing.id, input);
1468
+ if (paymentAdapter && !existing.providerCustomerIds[paymentAdapter.provider]) {
1469
+ try {
1470
+ const email = input.email ?? existing.email;
1471
+ const name = input.name !== void 0 ? input.name : existing.name;
1472
+ const metadata = input.metadata ?? existing.metadata;
1473
+ const externalId = input.externalId ?? existing.externalId;
1474
+ const providerInput = {
1475
+ email,
1476
+ externalId
1477
+ };
1478
+ if (name !== void 0) providerInput.name = name;
1479
+ if (metadata) providerInput.metadata = metadata;
1480
+ const providerCustomerId = await paymentAdapter.customers.create(providerInput);
1481
+ const updated2 = await storage.customers.update(existing.id, {
1482
+ ...input,
1483
+ providerCustomerIds: {
1484
+ ...existing.providerCustomerIds,
1485
+ [paymentAdapter.provider]: providerCustomerId
1486
+ }
1487
+ });
1488
+ if (updated2) {
1489
+ await emitter.emit("customer.updated", updated2);
1490
+ }
1491
+ return updated2 ?? existing;
1492
+ } catch (error) {
1493
+ this.logger.error("Failed to sync existing customer with provider", {
1494
+ provider: paymentAdapter.provider,
1495
+ customerId: existing.id,
1496
+ error: error instanceof Error ? error.message : String(error)
1497
+ });
1498
+ }
1499
+ }
1500
+ const updated = await storage.customers.update(existing.id, input);
1337
1501
  if (updated) {
1338
- await this.emitter.emit("customer.updated", updated);
1502
+ await emitter.emit("customer.updated", updated);
1339
1503
  }
1340
1504
  return updated ?? existing;
1341
1505
  }
1342
- const customer = await this.storage.customers.create(input);
1343
- await this.emitter.emit("customer.created", customer);
1506
+ const customer = await storage.customers.create(input);
1507
+ if (paymentAdapter) {
1508
+ try {
1509
+ const providerInput = {
1510
+ email: input.email,
1511
+ externalId: input.externalId
1512
+ };
1513
+ if (input.name !== void 0) providerInput.name = input.name;
1514
+ if (input.metadata) providerInput.metadata = input.metadata;
1515
+ const providerCustomerId = await paymentAdapter.customers.create(providerInput);
1516
+ const updated = await storage.customers.update(customer.id, {
1517
+ providerCustomerIds: {
1518
+ ...customer.providerCustomerIds,
1519
+ [paymentAdapter.provider]: providerCustomerId
1520
+ }
1521
+ });
1522
+ await emitter.emit("customer.created", updated ?? customer);
1523
+ return updated ?? customer;
1524
+ } catch (error) {
1525
+ this.logger.error("Failed to sync new customer with provider", {
1526
+ provider: paymentAdapter.provider,
1527
+ error: error instanceof Error ? error.message : String(error)
1528
+ });
1529
+ await emitter.emit("customer.created", customer);
1530
+ return customer;
1531
+ }
1532
+ }
1533
+ await emitter.emit("customer.created", customer);
1344
1534
  return customer;
1345
1535
  }
1346
1536
  };
@@ -1416,13 +1606,29 @@ var QZPayBillingImpl = class {
1416
1606
  if (!currentSubscription) {
1417
1607
  throw new Error(`Subscription ${id} not found`);
1418
1608
  }
1419
- const currentPlan = planMap.get(currentSubscription.planId);
1420
- const currentPrice = currentPlan?.prices[0] ?? null;
1421
- const newPlan = planMap.get(options.newPlanId);
1609
+ let currentPlan = planMap.get(currentSubscription.planId);
1610
+ if (!currentPlan) {
1611
+ currentPlan = await storage.plans.findById(currentSubscription.planId) ?? void 0;
1612
+ }
1613
+ let currentPrice = currentPlan?.prices[0] ?? null;
1614
+ if (currentPlan && (!currentPlan.prices || currentPlan.prices.length === 0)) {
1615
+ const storagePrices = await storage.prices.findByPlanId(currentSubscription.planId);
1616
+ if (storagePrices.length > 0) {
1617
+ currentPrice = storagePrices[0] ?? null;
1618
+ }
1619
+ }
1620
+ let newPlan = planMap.get(options.newPlanId);
1621
+ if (!newPlan) {
1622
+ newPlan = await storage.plans.findById(options.newPlanId) ?? void 0;
1623
+ }
1422
1624
  if (!newPlan) {
1423
1625
  throw new Error(`Plan ${options.newPlanId} not found`);
1424
1626
  }
1425
- const newPrice = options.newPriceId ? newPlan.prices.find((p) => p.id === options.newPriceId) : newPlan.prices[0];
1627
+ let newPlanPrices = newPlan.prices || [];
1628
+ if (newPlanPrices.length === 0) {
1629
+ newPlanPrices = await storage.prices.findByPlanId(options.newPlanId);
1630
+ }
1631
+ const newPrice = options.newPriceId ? newPlanPrices.find((p) => p.id === options.newPriceId) : newPlanPrices[0];
1426
1632
  if (!newPrice) {
1427
1633
  throw new Error(`Price not found for plan ${options.newPlanId}`);
1428
1634
  }
@@ -1476,6 +1682,7 @@ var QZPayBillingImpl = class {
1476
1682
  return {
1477
1683
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complex payment processing with provider integration and error handling
1478
1684
  process: async (input) => {
1685
+ qzpayCreateValidator(input).required("amount", "Amount is required").custom(typeof input.amount === "number" && input.amount > 0, "Amount must be greater than 0").required("currency", "Currency is required").currency("currency", "Invalid currency code").assertValid();
1479
1686
  const paymentId = crypto.randomUUID();
1480
1687
  const now = /* @__PURE__ */ new Date();
1481
1688
  const payment = await storage.payments.create({
@@ -1511,6 +1718,15 @@ var QZPayBillingImpl = class {
1511
1718
  if (input.subscriptionId !== void 0) paymentInput.subscriptionId = input.subscriptionId;
1512
1719
  if (input.invoiceId !== void 0) paymentInput.invoiceId = input.invoiceId;
1513
1720
  if (input.metadata !== void 0) paymentInput.metadata = input.metadata;
1721
+ if (input.token !== void 0) paymentInput.token = input.token;
1722
+ if (input.cardId !== void 0) paymentInput.cardId = input.cardId;
1723
+ if (input.installments !== void 0) paymentInput.installments = input.installments;
1724
+ if (input.payerEmail) {
1725
+ paymentInput.payerEmail = input.payerEmail;
1726
+ } else if (customer?.email) {
1727
+ paymentInput.payerEmail = customer.email;
1728
+ }
1729
+ if (input.payerIdentification) paymentInput.payerIdentification = input.payerIdentification;
1514
1730
  const result = await paymentAdapter.payments.create(providerCustomerId, paymentInput);
1515
1731
  const updated = await storage.payments.update(paymentId, {
1516
1732
  status: "succeeded",
@@ -1518,8 +1734,20 @@ var QZPayBillingImpl = class {
1518
1734
  });
1519
1735
  await emitter.emit("payment.succeeded", updated);
1520
1736
  return updated;
1521
- } catch {
1522
- const failed = await storage.payments.update(paymentId, { status: "failed" });
1737
+ } catch (error) {
1738
+ const errorMessage = error instanceof Error ? error.message : "Unknown payment error";
1739
+ this.logger.error("Payment processing failed", {
1740
+ provider: paymentAdapter.provider,
1741
+ customerId: input.customerId,
1742
+ amount: input.amount,
1743
+ currency: input.currency,
1744
+ error: errorMessage
1745
+ });
1746
+ const failed = await storage.payments.update(paymentId, {
1747
+ status: "failed",
1748
+ failureMessage: errorMessage,
1749
+ failureCode: "payment_failed"
1750
+ });
1523
1751
  await emitter.emit("payment.failed", failed);
1524
1752
  return failed;
1525
1753
  }
@@ -1528,6 +1756,39 @@ var QZPayBillingImpl = class {
1528
1756
  },
1529
1757
  get: (id) => storage.payments.findById(id),
1530
1758
  getByCustomerId: (customerId) => storage.payments.findByCustomerId(customerId),
1759
+ /**
1760
+ * Record an external payment (already processed by a payment provider)
1761
+ * Use this when the payment was processed by an external system (e.g., backend API)
1762
+ * and you need to create a local record for tracking purposes.
1763
+ */
1764
+ record: async (input) => {
1765
+ const now = /* @__PURE__ */ new Date();
1766
+ const providerPaymentIds = {};
1767
+ if (input.provider && input.providerPaymentId) {
1768
+ providerPaymentIds[input.provider] = input.providerPaymentId;
1769
+ }
1770
+ const payment = await storage.payments.create({
1771
+ id: input.id,
1772
+ customerId: input.customerId,
1773
+ amount: input.amount,
1774
+ currency: input.currency,
1775
+ status: input.status,
1776
+ invoiceId: input.invoiceId ?? null,
1777
+ subscriptionId: input.subscriptionId ?? null,
1778
+ paymentMethodId: null,
1779
+ failureCode: null,
1780
+ failureMessage: null,
1781
+ metadata: input.metadata ?? {},
1782
+ livemode: this.livemode,
1783
+ createdAt: now,
1784
+ updatedAt: now,
1785
+ providerPaymentIds
1786
+ });
1787
+ if (input.status === "succeeded") {
1788
+ await emitter.emit("payment.succeeded", payment);
1789
+ }
1790
+ return payment;
1791
+ },
1531
1792
  refund: async (input) => {
1532
1793
  const payment = await storage.payments.findById(input.paymentId);
1533
1794
  if (!payment) {
@@ -1549,6 +1810,10 @@ var QZPayBillingImpl = class {
1549
1810
  const emitter = this.emitter;
1550
1811
  return {
1551
1812
  create: async (input) => {
1813
+ qzpayCreateValidator(input).required("customerId", "Customer ID is required").required("lines", "Invoice lines are required").custom(Array.isArray(input.lines) && input.lines.length > 0, "Invoice must have at least one line item").assertValid();
1814
+ for (const line of input.lines) {
1815
+ qzpayCreateValidator(line).custom(typeof line.quantity === "number" && line.quantity > 0, "Line quantity must be greater than 0").custom(typeof line.unitAmount === "number" && line.unitAmount >= 0, "Line unit amount must be non-negative").assertValid();
1816
+ }
1552
1817
  const invoice = await storage.invoices.create({
1553
1818
  id: crypto.randomUUID(),
1554
1819
  customerId: input.customerId,
@@ -1601,7 +1866,7 @@ var QZPayBillingImpl = class {
1601
1866
  get promoCodes() {
1602
1867
  const storage = this.storage;
1603
1868
  return {
1604
- validate: async (code, _customerId, _planId) => {
1869
+ validate: async (code, customerId, planId) => {
1605
1870
  const promoCode = await storage.promoCodes.findByCode(code);
1606
1871
  if (!promoCode) {
1607
1872
  return { valid: false, promoCode: null, error: "Promo code not found" };
@@ -1609,12 +1874,26 @@ var QZPayBillingImpl = class {
1609
1874
  if (!promoCode.active) {
1610
1875
  return { valid: false, promoCode, error: "Promo code is not active" };
1611
1876
  }
1612
- if (promoCode.validUntil && /* @__PURE__ */ new Date() > promoCode.validUntil) {
1877
+ const now = /* @__PURE__ */ new Date();
1878
+ if (promoCode.validFrom && now < promoCode.validFrom) {
1879
+ return { valid: false, promoCode, error: "Promo code is not yet valid" };
1880
+ }
1881
+ if (promoCode.validUntil && now > promoCode.validUntil) {
1613
1882
  return { valid: false, promoCode, error: "Promo code has expired" };
1614
1883
  }
1615
1884
  if (promoCode.maxRedemptions && promoCode.currentRedemptions >= promoCode.maxRedemptions) {
1616
1885
  return { valid: false, promoCode, error: "Promo code has reached max redemptions" };
1617
1886
  }
1887
+ if (planId && promoCode.applicablePlanIds.length > 0 && !promoCode.applicablePlanIds.includes(planId)) {
1888
+ return { valid: false, promoCode, error: "Promo code is not applicable to this plan" };
1889
+ }
1890
+ if (customerId && promoCode.maxRedemptionsPerCustomer !== null && promoCode.maxRedemptionsPerCustomer > 0) {
1891
+ const customerSubscriptions = await storage.subscriptions.findByCustomerId(customerId);
1892
+ const redemptionCount = customerSubscriptions.filter((sub) => sub.promoCodeId === promoCode.id).length;
1893
+ if (redemptionCount >= promoCode.maxRedemptionsPerCustomer) {
1894
+ return { valid: false, promoCode, error: "You have reached the maximum redemption limit for this promo code" };
1895
+ }
1896
+ }
1618
1897
  const result = {
1619
1898
  valid: true,
1620
1899
  promoCode
@@ -1767,6 +2046,10 @@ var QZPayBillingImpl = class {
1767
2046
  const emitter = this.emitter;
1768
2047
  return {
1769
2048
  create: async (input) => {
2049
+ qzpayCreateValidator(input).required("name", "Add-on name is required").required("unitAmount", "Unit amount is required").custom(typeof input.unitAmount === "number" && input.unitAmount > 0, "Unit amount must be greater than 0").required("currency", "Currency is required").currency("currency", "Invalid currency code").custom(
2050
+ !input.billingIntervalCount || typeof input.billingIntervalCount === "number" && input.billingIntervalCount > 0,
2051
+ "Billing interval count must be greater than 0"
2052
+ ).assertValid();
1770
2053
  const addon = await storage.addons.create({
1771
2054
  id: input.id ?? crypto.randomUUID(),
1772
2055
  ...input
@@ -6768,108 +7051,6 @@ function createHealthService(config) {
6768
7051
  return new QZPayHealthService(config);
6769
7052
  }
6770
7053
 
6771
- // src/utils/validation.utils.ts
6772
- function qzpayIsValidEmail(email) {
6773
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6774
- return emailRegex.test(email);
6775
- }
6776
- function qzpayIsValidCurrency(currency) {
6777
- return QZPAY_CURRENCY_VALUES.includes(currency);
6778
- }
6779
- function qzpayIsPositiveInteger(value) {
6780
- return Number.isInteger(value) && value > 0;
6781
- }
6782
- function qzpayIsNonNegativeInteger(value) {
6783
- return Number.isInteger(value) && value >= 0;
6784
- }
6785
- function qzpayIsValidPercentage(value) {
6786
- return value >= 0 && value <= 100;
6787
- }
6788
- function qzpayIsValidUuid(value) {
6789
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6790
- return uuidRegex.test(value);
6791
- }
6792
- function qzpayIsRequiredString(value) {
6793
- return typeof value === "string" && value.trim().length > 0;
6794
- }
6795
- function qzpayValidateRequired(obj, requiredFields) {
6796
- const errors = [];
6797
- for (const field of requiredFields) {
6798
- const value = obj[field];
6799
- if (value === void 0 || value === null || value === "") {
6800
- errors.push(`${String(field)} is required`);
6801
- }
6802
- }
6803
- return {
6804
- valid: errors.length === 0,
6805
- errors
6806
- };
6807
- }
6808
- function qzpayAssert(condition, message) {
6809
- if (!condition) {
6810
- throw new Error(message);
6811
- }
6812
- }
6813
- function qzpayAssertDefined(value, name) {
6814
- if (value === null || value === void 0) {
6815
- throw new Error(`${name} is required`);
6816
- }
6817
- }
6818
- function qzpayCreateValidator(obj) {
6819
- return new QZPayValidator(obj);
6820
- }
6821
- var QZPayValidator = class {
6822
- constructor(obj) {
6823
- this.obj = obj;
6824
- }
6825
- errors = [];
6826
- required(field, message) {
6827
- const value = this.obj[field];
6828
- if (value === void 0 || value === null || value === "") {
6829
- this.errors.push(message ?? `${String(field)} is required`);
6830
- }
6831
- return this;
6832
- }
6833
- email(field, message) {
6834
- const value = this.obj[field];
6835
- if (typeof value === "string" && !qzpayIsValidEmail(value)) {
6836
- this.errors.push(message ?? `${String(field)} must be a valid email`);
6837
- }
6838
- return this;
6839
- }
6840
- positiveInteger(field, message) {
6841
- const value = this.obj[field];
6842
- if (typeof value === "number" && !qzpayIsPositiveInteger(value)) {
6843
- this.errors.push(message ?? `${String(field)} must be a positive integer`);
6844
- }
6845
- return this;
6846
- }
6847
- currency(field, message) {
6848
- const value = this.obj[field];
6849
- if (typeof value === "string" && !qzpayIsValidCurrency(value)) {
6850
- this.errors.push(message ?? `${String(field)} must be a valid currency`);
6851
- }
6852
- return this;
6853
- }
6854
- custom(condition, message) {
6855
- if (!condition) {
6856
- this.errors.push(message);
6857
- }
6858
- return this;
6859
- }
6860
- validate() {
6861
- return {
6862
- valid: this.errors.length === 0,
6863
- errors: [...this.errors]
6864
- };
6865
- }
6866
- assertValid() {
6867
- if (this.errors.length > 0) {
6868
- throw new Error(`Validation failed: ${this.errors.join(", ")}`);
6869
- }
6870
- }
6871
- };
6872
-
6873
7054
  exports.QZPAY_BILLING_EVENT = QZPAY_BILLING_EVENT;
6874
7055
  exports.QZPAY_BILLING_EVENT_VALUES = QZPAY_BILLING_EVENT_VALUES;
6875
7056
  exports.QZPAY_BILLING_INTERVAL = QZPAY_BILLING_INTERVAL;