billsdk 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createDefaultTimeProvider } from '@billsdk/core';
1
2
  export * from '@billsdk/core';
2
3
  export { drizzleAdapter } from '@billsdk/drizzle-adapter';
3
4
  import { memoryAdapter } from '@billsdk/memory-adapter';
@@ -325,7 +326,8 @@ function calculateProration(params) {
325
326
  changeDate = /* @__PURE__ */ new Date()
326
327
  } = params;
327
328
  const totalDays = daysBetween(currentPeriodStart, currentPeriodEnd);
328
- const daysRemaining = daysBetween(changeDate, currentPeriodEnd);
329
+ const daysUsed = daysBetween(currentPeriodStart, changeDate);
330
+ const daysRemaining = totalDays - daysUsed;
329
331
  const effectiveDaysRemaining = Math.max(
330
332
  0,
331
333
  Math.min(daysRemaining, totalDays)
@@ -333,15 +335,13 @@ function calculateProration(params) {
333
335
  const credit = Math.round(
334
336
  oldPlanAmount / totalDays * effectiveDaysRemaining
335
337
  );
336
- const charge = Math.round(
337
- newPlanAmount / totalDays * effectiveDaysRemaining
338
- );
339
- const netAmount = charge - credit;
338
+ const charge = newPlanAmount;
339
+ const netAmount = Math.max(0, charge - credit);
340
340
  return {
341
341
  credit,
342
342
  charge,
343
343
  netAmount,
344
- daysRemaining: effectiveDaysRemaining,
344
+ daysUsed: Math.max(0, Math.min(daysUsed, totalDays)),
345
345
  totalDays
346
346
  };
347
347
  }
@@ -490,8 +490,19 @@ async function cancelSubscription(ctx, params) {
490
490
  accessUntil: subscription.currentPeriodEnd
491
491
  };
492
492
  }
493
+ var intervalRank = {
494
+ monthly: 1,
495
+ quarterly: 2,
496
+ yearly: 3
497
+ };
498
+ function isUpgrade(oldPrice, newPrice, oldInterval, newInterval) {
499
+ if (oldInterval !== newInterval) {
500
+ return intervalRank[newInterval] > intervalRank[oldInterval];
501
+ }
502
+ return newPrice > oldPrice;
503
+ }
493
504
  async function changeSubscription(ctx, params) {
494
- const { customerId, newPlanCode, prorate = true } = params;
505
+ const { customerId, newPlanCode, newInterval, prorate = true } = params;
495
506
  const customer = await ctx.internalAdapter.findCustomerByExternalId(customerId);
496
507
  if (!customer) {
497
508
  throw new Error("Customer not found");
@@ -502,7 +513,8 @@ async function changeSubscription(ctx, params) {
502
513
  if (!subscription) {
503
514
  throw new Error("No active subscription found");
504
515
  }
505
- if (subscription.planCode === newPlanCode) {
516
+ const targetInterval = newInterval ?? subscription.interval;
517
+ if (subscription.planCode === newPlanCode && subscription.interval === targetInterval) {
506
518
  throw new Error("Already on this plan");
507
519
  }
508
520
  const oldPlan = ctx.internalAdapter.findPlanByCode(subscription.planCode);
@@ -519,27 +531,67 @@ async function changeSubscription(ctx, params) {
519
531
  }
520
532
  const newPrice = ctx.internalAdapter.getPlanPrice(
521
533
  newPlanCode,
522
- subscription.interval
534
+ targetInterval
523
535
  );
524
536
  if (!newPrice) {
525
537
  throw new Error(
526
- `No price found for plan ${newPlanCode} with interval ${subscription.interval}`
538
+ `No price found for plan ${newPlanCode} with interval ${targetInterval}`
539
+ );
540
+ }
541
+ const upgrade = isUpgrade(
542
+ oldPrice.amount,
543
+ newPrice.amount,
544
+ subscription.interval,
545
+ targetInterval
546
+ );
547
+ ctx.logger.info("Plan change detected", {
548
+ from: `${subscription.planCode} (${subscription.interval})`,
549
+ to: `${newPlanCode} (${targetInterval})`,
550
+ oldAmount: oldPrice.amount,
551
+ newAmount: newPrice.amount,
552
+ isUpgrade: upgrade
553
+ });
554
+ if (!upgrade) {
555
+ ctx.logger.info("Downgrade scheduled for period end", {
556
+ scheduledPlanCode: newPlanCode,
557
+ scheduledInterval: targetInterval,
558
+ effectiveAt: subscription.currentPeriodEnd
559
+ });
560
+ const updatedSubscription2 = await ctx.internalAdapter.updateSubscription(
561
+ subscription.id,
562
+ {
563
+ scheduledPlanCode: newPlanCode,
564
+ scheduledInterval: targetInterval
565
+ }
527
566
  );
567
+ return {
568
+ subscription: updatedSubscription2,
569
+ previousPlan: oldPlan,
570
+ newPlan,
571
+ payment: null,
572
+ scheduled: true,
573
+ effectiveAt: subscription.currentPeriodEnd
574
+ };
528
575
  }
529
576
  let payment = null;
577
+ const changeDate = await ctx.timeProvider.now(params.customerId);
578
+ ctx.logger.info("changeDate from timeProvider", {
579
+ changeDate: changeDate.toISOString(),
580
+ realTime: (/* @__PURE__ */ new Date()).toISOString()
581
+ });
530
582
  if (prorate) {
531
583
  const prorationResult = calculateProration({
532
584
  oldPlanAmount: oldPrice.amount,
533
585
  newPlanAmount: newPrice.amount,
534
586
  currentPeriodStart: subscription.currentPeriodStart,
535
587
  currentPeriodEnd: subscription.currentPeriodEnd,
536
- changeDate: /* @__PURE__ */ new Date()
588
+ changeDate
537
589
  });
538
590
  ctx.logger.info("Proration calculated", {
539
591
  credit: prorationResult.credit,
540
592
  charge: prorationResult.charge,
541
593
  netAmount: prorationResult.netAmount,
542
- daysRemaining: prorationResult.daysRemaining
594
+ daysUsed: prorationResult.daysUsed
543
595
  });
544
596
  if (prorationResult.netAmount > 0) {
545
597
  if (!ctx.paymentAdapter?.charge) {
@@ -591,15 +643,36 @@ async function changeSubscription(ctx, params) {
591
643
  });
592
644
  }
593
645
  }
646
+ const newPeriodStart = changeDate;
647
+ const newPeriodEnd = new Date(changeDate);
648
+ if (targetInterval === "yearly") {
649
+ newPeriodEnd.setFullYear(newPeriodEnd.getFullYear() + 1);
650
+ } else if (targetInterval === "quarterly") {
651
+ newPeriodEnd.setMonth(newPeriodEnd.getMonth() + 3);
652
+ } else {
653
+ newPeriodEnd.setMonth(newPeriodEnd.getMonth() + 1);
654
+ }
594
655
  const updatedSubscription = await ctx.internalAdapter.updateSubscription(
595
656
  subscription.id,
596
- { planCode: newPlanCode }
657
+ {
658
+ planCode: newPlanCode,
659
+ interval: targetInterval,
660
+ currentPeriodStart: newPeriodStart,
661
+ currentPeriodEnd: newPeriodEnd,
662
+ scheduledPlanCode: void 0,
663
+ scheduledInterval: void 0
664
+ }
597
665
  );
666
+ ctx.logger.info("Period reset", {
667
+ from: `${subscription.currentPeriodStart.toISOString()} - ${subscription.currentPeriodEnd.toISOString()}`,
668
+ to: `${newPeriodStart.toISOString()} - ${newPeriodEnd.toISOString()}`
669
+ });
598
670
  return {
599
671
  subscription: updatedSubscription,
600
672
  previousPlan: oldPlan,
601
673
  newPlan,
602
- payment
674
+ payment,
675
+ scheduled: false
603
676
  };
604
677
  }
605
678
 
@@ -762,6 +835,326 @@ var refundEndpoints = {
762
835
  }
763
836
  }
764
837
  };
838
+
839
+ // src/logic/renewal-service.ts
840
+ function calculateNewPeriodEnd(from, interval) {
841
+ const newEnd = new Date(from);
842
+ if (interval === "yearly") {
843
+ newEnd.setFullYear(newEnd.getFullYear() + 1);
844
+ } else if (interval === "quarterly") {
845
+ newEnd.setMonth(newEnd.getMonth() + 3);
846
+ } else {
847
+ newEnd.setMonth(newEnd.getMonth() + 1);
848
+ }
849
+ return newEnd;
850
+ }
851
+ async function findDueSubscriptions(ctx, params) {
852
+ const now = params.customerId ? await ctx.timeProvider.now(params.customerId) : /* @__PURE__ */ new Date();
853
+ const where = [
854
+ // Only active or past_due subscriptions
855
+ {
856
+ field: "status",
857
+ operator: "in",
858
+ value: ["active", "past_due"]
859
+ },
860
+ // Period has ended
861
+ {
862
+ field: "currentPeriodEnd",
863
+ operator: "lte",
864
+ value: now
865
+ }
866
+ ];
867
+ if (params.customerId) {
868
+ const customer = await ctx.internalAdapter.findCustomerByExternalId(
869
+ params.customerId
870
+ );
871
+ if (!customer) {
872
+ return [];
873
+ }
874
+ where.push({
875
+ field: "customerId",
876
+ operator: "eq",
877
+ value: customer.id
878
+ });
879
+ }
880
+ return ctx.adapter.findMany({
881
+ model: TABLES.SUBSCRIPTION,
882
+ where,
883
+ limit: params.limit,
884
+ sortBy: { field: "currentPeriodEnd", direction: "asc" }
885
+ });
886
+ }
887
+ async function applyScheduledChanges(ctx, subscription) {
888
+ if (!subscription.scheduledPlanCode) {
889
+ return {
890
+ planChanged: false,
891
+ newPlanCode: subscription.planCode,
892
+ newInterval: subscription.interval
893
+ };
894
+ }
895
+ const newPlanCode = subscription.scheduledPlanCode;
896
+ const newInterval = subscription.scheduledInterval ?? subscription.interval;
897
+ ctx.logger.info("Applying scheduled plan change", {
898
+ subscriptionId: subscription.id,
899
+ from: subscription.planCode,
900
+ to: newPlanCode,
901
+ newInterval
902
+ });
903
+ await ctx.internalAdapter.updateSubscription(subscription.id, {
904
+ planCode: newPlanCode,
905
+ interval: newInterval,
906
+ scheduledPlanCode: void 0,
907
+ scheduledInterval: void 0
908
+ });
909
+ return {
910
+ planChanged: true,
911
+ newPlanCode,
912
+ newInterval
913
+ };
914
+ }
915
+ async function processSubscriptionRenewal(ctx, subscription, dryRun) {
916
+ const customer = await ctx.internalAdapter.findCustomerById(
917
+ subscription.customerId
918
+ );
919
+ if (!customer) {
920
+ return {
921
+ subscriptionId: subscription.id,
922
+ customerId: subscription.customerId,
923
+ status: "failed",
924
+ error: "Customer not found"
925
+ };
926
+ }
927
+ const {
928
+ planChanged,
929
+ newPlanCode,
930
+ newInterval: newIntervalStr
931
+ } = await applyScheduledChanges(ctx, subscription);
932
+ const newInterval = newIntervalStr;
933
+ const price = ctx.internalAdapter.getPlanPrice(newPlanCode, newInterval);
934
+ if (!price) {
935
+ return {
936
+ subscriptionId: subscription.id,
937
+ customerId: customer.externalId,
938
+ status: "failed",
939
+ error: `No price found for plan ${newPlanCode} with interval ${newInterval}`,
940
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
941
+ };
942
+ }
943
+ const amount = price.amount;
944
+ const now = await ctx.timeProvider.now(customer.externalId);
945
+ if (dryRun) {
946
+ return {
947
+ subscriptionId: subscription.id,
948
+ customerId: customer.externalId,
949
+ status: "succeeded",
950
+ amount,
951
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
952
+ };
953
+ }
954
+ if (amount === 0) {
955
+ const newPeriodEnd2 = calculateNewPeriodEnd(now, newInterval);
956
+ await ctx.internalAdapter.updateSubscription(subscription.id, {
957
+ currentPeriodStart: now,
958
+ currentPeriodEnd: newPeriodEnd2,
959
+ status: "active"
960
+ });
961
+ ctx.logger.info("Free renewal processed", {
962
+ subscriptionId: subscription.id,
963
+ newPeriodEnd: newPeriodEnd2.toISOString()
964
+ });
965
+ return {
966
+ subscriptionId: subscription.id,
967
+ customerId: customer.externalId,
968
+ status: "succeeded",
969
+ amount: 0,
970
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
971
+ };
972
+ }
973
+ if (!customer.providerCustomerId) {
974
+ ctx.logger.warn("Customer has no payment method, marking as past_due", {
975
+ subscriptionId: subscription.id,
976
+ customerId: customer.externalId
977
+ });
978
+ await ctx.internalAdapter.updateSubscription(subscription.id, {
979
+ status: "past_due"
980
+ });
981
+ return {
982
+ subscriptionId: subscription.id,
983
+ customerId: customer.externalId,
984
+ status: "failed",
985
+ error: "No payment method on file",
986
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
987
+ };
988
+ }
989
+ if (!ctx.paymentAdapter?.charge) {
990
+ ctx.logger.error("Payment adapter does not support direct charging", {
991
+ subscriptionId: subscription.id
992
+ });
993
+ return {
994
+ subscriptionId: subscription.id,
995
+ customerId: customer.externalId,
996
+ status: "failed",
997
+ error: "Payment adapter does not support direct charging",
998
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
999
+ };
1000
+ }
1001
+ const plan = ctx.internalAdapter.findPlanByCode(newPlanCode);
1002
+ const chargeResult = await ctx.paymentAdapter.charge({
1003
+ customer: {
1004
+ id: customer.id,
1005
+ email: customer.email,
1006
+ providerCustomerId: customer.providerCustomerId
1007
+ },
1008
+ amount,
1009
+ currency: price.currency,
1010
+ description: `Renewal: ${plan?.name ?? newPlanCode} (${newInterval})`,
1011
+ metadata: {
1012
+ subscriptionId: subscription.id,
1013
+ customerId: customer.id,
1014
+ type: "renewal",
1015
+ planCode: newPlanCode
1016
+ }
1017
+ });
1018
+ if (chargeResult.status === "failed") {
1019
+ ctx.logger.warn("Renewal charge failed", {
1020
+ subscriptionId: subscription.id,
1021
+ error: chargeResult.error
1022
+ });
1023
+ await ctx.internalAdapter.updateSubscription(subscription.id, {
1024
+ status: "past_due"
1025
+ });
1026
+ return {
1027
+ subscriptionId: subscription.id,
1028
+ customerId: customer.externalId,
1029
+ status: "failed",
1030
+ error: chargeResult.error ?? "Charge failed",
1031
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
1032
+ };
1033
+ }
1034
+ const newPeriodEnd = calculateNewPeriodEnd(now, newInterval);
1035
+ await ctx.internalAdapter.updateSubscription(subscription.id, {
1036
+ currentPeriodStart: now,
1037
+ currentPeriodEnd: newPeriodEnd,
1038
+ status: "active"
1039
+ });
1040
+ await ctx.internalAdapter.createPayment({
1041
+ customerId: customer.id,
1042
+ subscriptionId: subscription.id,
1043
+ type: "renewal",
1044
+ status: "succeeded",
1045
+ amount,
1046
+ currency: price.currency,
1047
+ providerPaymentId: chargeResult.providerPaymentId,
1048
+ metadata: {
1049
+ planCode: newPlanCode,
1050
+ interval: newInterval
1051
+ }
1052
+ });
1053
+ ctx.logger.info("Renewal succeeded", {
1054
+ subscriptionId: subscription.id,
1055
+ amount,
1056
+ newPeriodEnd: newPeriodEnd.toISOString()
1057
+ });
1058
+ return {
1059
+ subscriptionId: subscription.id,
1060
+ customerId: customer.externalId,
1061
+ status: "succeeded",
1062
+ amount,
1063
+ planChanged: planChanged ? { from: subscription.planCode, to: newPlanCode } : void 0
1064
+ };
1065
+ }
1066
+ async function processRenewals(ctx, params = {}) {
1067
+ const { dryRun = false } = params;
1068
+ ctx.logger.info("Starting renewal processing", {
1069
+ dryRun,
1070
+ customerId: params.customerId,
1071
+ limit: params.limit
1072
+ });
1073
+ const dueSubscriptions = await findDueSubscriptions(ctx, params);
1074
+ ctx.logger.info(
1075
+ `Found ${dueSubscriptions.length} subscriptions due for renewal`
1076
+ );
1077
+ const result = {
1078
+ processed: 0,
1079
+ succeeded: 0,
1080
+ failed: 0,
1081
+ skipped: 0,
1082
+ renewals: []
1083
+ };
1084
+ for (const subscription of dueSubscriptions) {
1085
+ result.processed++;
1086
+ try {
1087
+ const renewalResult = await processSubscriptionRenewal(
1088
+ ctx,
1089
+ subscription,
1090
+ dryRun
1091
+ );
1092
+ result.renewals.push(renewalResult);
1093
+ if (renewalResult.status === "succeeded") {
1094
+ result.succeeded++;
1095
+ } else if (renewalResult.status === "failed") {
1096
+ result.failed++;
1097
+ } else {
1098
+ result.skipped++;
1099
+ }
1100
+ } catch (error) {
1101
+ ctx.logger.error("Error processing renewal", {
1102
+ subscriptionId: subscription.id,
1103
+ error: error instanceof Error ? error.message : String(error)
1104
+ });
1105
+ result.failed++;
1106
+ result.renewals.push({
1107
+ subscriptionId: subscription.id,
1108
+ customerId: subscription.customerId,
1109
+ status: "failed",
1110
+ error: error instanceof Error ? error.message : "Unknown error"
1111
+ });
1112
+ }
1113
+ }
1114
+ ctx.logger.info("Renewal processing complete", {
1115
+ processed: result.processed,
1116
+ succeeded: result.succeeded,
1117
+ failed: result.failed,
1118
+ skipped: result.skipped
1119
+ });
1120
+ return result;
1121
+ }
1122
+
1123
+ // src/api/routes/renewals.ts
1124
+ var processRenewalsQuerySchema = z.object({
1125
+ /**
1126
+ * Process only a specific customer (useful for testing)
1127
+ */
1128
+ customerId: z.string().optional(),
1129
+ /**
1130
+ * Dry run - don't actually charge, just report what would happen
1131
+ */
1132
+ dryRun: z.string().transform((val) => val === "true").optional(),
1133
+ /**
1134
+ * Maximum number of subscriptions to process (for batching)
1135
+ */
1136
+ limit: z.string().transform((val) => Number.parseInt(val, 10)).refine((val) => !Number.isNaN(val) && val > 0, {
1137
+ message: "limit must be a positive number"
1138
+ }).optional()
1139
+ });
1140
+ var renewalEndpoints = {
1141
+ processRenewals: {
1142
+ path: "/renewals",
1143
+ options: {
1144
+ method: "GET",
1145
+ query: processRenewalsQuerySchema
1146
+ },
1147
+ handler: async (context) => {
1148
+ const { ctx, query } = context;
1149
+ const result = await processRenewals(ctx, {
1150
+ customerId: query.customerId,
1151
+ dryRun: query.dryRun ?? false,
1152
+ limit: query.limit
1153
+ });
1154
+ return result;
1155
+ }
1156
+ }
1157
+ };
765
1158
  var getSubscriptionQuerySchema = z.object({
766
1159
  customerId: z.string().min(1)
767
1160
  });
@@ -884,7 +1277,8 @@ var webhookEndpoints = {
884
1277
  }
885
1278
  ctx.logger.debug("Payment confirmation received", {
886
1279
  subscriptionId: result.subscriptionId,
887
- status: result.status
1280
+ status: result.status,
1281
+ amount: result.amount
888
1282
  });
889
1283
  if (result.status === "active") {
890
1284
  const subscription = await ctx.internalAdapter.findSubscriptionById(
@@ -917,6 +1311,28 @@ var webhookEndpoints = {
917
1311
  });
918
1312
  }
919
1313
  }
1314
+ if (result.amount && result.amount > 0) {
1315
+ await ctx.internalAdapter.createPayment({
1316
+ customerId: subscription.customerId,
1317
+ subscriptionId: subscription.id,
1318
+ type: "subscription",
1319
+ status: "succeeded",
1320
+ amount: result.amount,
1321
+ currency: result.currency ?? "usd",
1322
+ providerPaymentId: result.providerPaymentId,
1323
+ metadata: {
1324
+ planCode: subscription.planCode,
1325
+ interval: subscription.interval,
1326
+ confirmedVia: "webhook"
1327
+ }
1328
+ });
1329
+ ctx.logger.info("Payment record created", {
1330
+ subscriptionId: subscription.id,
1331
+ amount: result.amount,
1332
+ currency: result.currency,
1333
+ providerPaymentId: result.providerPaymentId
1334
+ });
1335
+ }
920
1336
  ctx.logger.info("Subscription activated via webhook", {
921
1337
  subscriptionId: subscription.id,
922
1338
  providerSubscriptionId: result.providerSubscriptionId
@@ -954,6 +1370,7 @@ function getEndpoints(ctx) {
954
1370
  ...featureEndpoints,
955
1371
  ...paymentEndpoints,
956
1372
  ...refundEndpoints,
1373
+ ...renewalEndpoints,
957
1374
  ...webhookEndpoints
958
1375
  };
959
1376
  let allEndpoints = { ...baseEndpoints };
@@ -1154,7 +1571,7 @@ function featureConfigToFeature(config) {
1154
1571
  type: config.type ?? "boolean"
1155
1572
  };
1156
1573
  }
1157
- function createInternalAdapter(adapter, plans = [], features = []) {
1574
+ function createInternalAdapter(adapter, plans = [], features = [], getNow = async () => /* @__PURE__ */ new Date()) {
1158
1575
  const plansByCode = /* @__PURE__ */ new Map();
1159
1576
  for (const config of plans) {
1160
1577
  plansByCode.set(config.code, planConfigToPlan(config));
@@ -1166,7 +1583,7 @@ function createInternalAdapter(adapter, plans = [], features = []) {
1166
1583
  return {
1167
1584
  // Customer operations (DB)
1168
1585
  async createCustomer(data) {
1169
- const now = /* @__PURE__ */ new Date();
1586
+ const now = await getNow();
1170
1587
  return adapter.create({
1171
1588
  model: TABLES.CUSTOMER,
1172
1589
  data: {
@@ -1238,7 +1655,7 @@ function createInternalAdapter(adapter, plans = [], features = []) {
1238
1655
  },
1239
1656
  // Subscription operations (DB)
1240
1657
  async createSubscription(data) {
1241
- const now = /* @__PURE__ */ new Date();
1658
+ const now = await getNow();
1242
1659
  const interval = data.interval ?? "monthly";
1243
1660
  const currentPeriodEnd = new Date(now);
1244
1661
  if (interval === "yearly") {
@@ -1308,14 +1725,15 @@ function createInternalAdapter(adapter, plans = [], features = []) {
1308
1725
  });
1309
1726
  },
1310
1727
  async updateSubscription(id, data) {
1728
+ const now = await getNow();
1311
1729
  return adapter.update({
1312
1730
  model: TABLES.SUBSCRIPTION,
1313
1731
  where: [{ field: "id", operator: "eq", value: id }],
1314
- update: { ...data, updatedAt: /* @__PURE__ */ new Date() }
1732
+ update: { ...data, updatedAt: now }
1315
1733
  });
1316
1734
  },
1317
1735
  async cancelSubscription(id, cancelAt) {
1318
- const now = /* @__PURE__ */ new Date();
1736
+ const now = await getNow();
1319
1737
  return adapter.update({
1320
1738
  model: TABLES.SUBSCRIPTION,
1321
1739
  where: [{ field: "id", operator: "eq", value: id }],
@@ -1358,7 +1776,7 @@ function createInternalAdapter(adapter, plans = [], features = []) {
1358
1776
  },
1359
1777
  // Payment operations (DB)
1360
1778
  async createPayment(data) {
1361
- const now = /* @__PURE__ */ new Date();
1779
+ const now = await getNow();
1362
1780
  return adapter.create({
1363
1781
  model: TABLES.PAYMENT,
1364
1782
  data: {
@@ -1467,10 +1885,12 @@ async function createBillingContext(adapter, options) {
1467
1885
  schema = { ...schema, ...plugin.schema };
1468
1886
  }
1469
1887
  }
1888
+ const getNow = async () => /* @__PURE__ */ new Date();
1470
1889
  const internalAdapter = createInternalAdapter(
1471
1890
  adapter,
1472
1891
  options.plans ?? [],
1473
- options.features ?? []
1892
+ options.features ?? [],
1893
+ getNow
1474
1894
  );
1475
1895
  const context = {
1476
1896
  options: resolvedOptions,
@@ -1482,6 +1902,7 @@ async function createBillingContext(adapter, options) {
1482
1902
  plugins,
1483
1903
  logger,
1484
1904
  secret: resolvedOptions.secret,
1905
+ timeProvider: createDefaultTimeProvider(),
1485
1906
  hasPlugin(id) {
1486
1907
  return plugins.some((p) => p.id === id);
1487
1908
  },
@@ -1621,6 +2042,10 @@ function createAPI(contextPromise) {
1621
2042
  amount: params.amount,
1622
2043
  reason: params.reason
1623
2044
  });
2045
+ },
2046
+ async processRenewals(params) {
2047
+ const ctx = await contextPromise;
2048
+ return processRenewals(ctx, params);
1624
2049
  }
1625
2050
  };
1626
2051
  }