@basaltkit/subscriptions 2.1.0 → 2.2.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/dist/index.d.ts CHANGED
@@ -161,6 +161,24 @@ declare class WebhookInvalidError extends BasaltError {
161
161
  readonly status = 400;
162
162
  constructor();
163
163
  }
164
+ /**
165
+ * Thrown when a gateway is asked to verify a webhook but no signing secret is
166
+ * configured. Verification fails closed: an unauthenticated callback must never
167
+ * be trusted (anyone could forge a `payment.succeeded`).
168
+ */
169
+ declare class WebhookSecretMissingError extends BasaltError {
170
+ readonly status = 500;
171
+ constructor(gateway: string);
172
+ }
173
+ /**
174
+ * Thrown when a confirmed payment's amount does not match the amount that was
175
+ * originally requested for that payment id — an underpayment, or a forged /
176
+ * mis-routed callback trying to settle an invoice for less.
177
+ */
178
+ declare class PaymentAmountMismatchError extends BasaltError {
179
+ readonly status = 400;
180
+ constructor(paymentId: string, expected: number, actual: number);
181
+ }
164
182
  /** Gateway-agnostic webhook event, already translated to domain terms. */
165
183
  interface WebhookEvent {
166
184
  /** Unique id at the gateway — used for idempotent processing. */
@@ -847,4 +865,4 @@ declare function billingRoutes(options: BillingRoutesOptions): BasaltRoute[];
847
865
  */
848
866
  declare function billingWebhookRoute(gateway: BillingGateway): BasaltRoute;
849
867
 
850
- export { type BillingGateway, type BillingPeriod, type BillingRoutesOptions, type CheckoutInput, type CreateSubscriptionInput, FakeBillingGateway, FakePaymentGateway, FeatureUnavailableError, type FeatureValue, GatewayUnsupportedError, type HandleEventResult, MemoryPaymentStore, MemoryRecurringStore, MemorySubscriptionStore, MemoryUsageStore, MemoryWebhookStore, type Meter, type NewPayment, NotSubscribedError, type PaymentApplyResult, type PaymentEvent, type PaymentGateway, type PaymentInstruction, PaymentLedger, type PaymentLedgerEvent, type PaymentLedgerEvents, type PaymentLedgerListener, type PaymentLedgerOptions, type PaymentRecord, type PaymentRecordStatus, type PaymentRequest, type PaymentStore, type PlanDefinition, type Plans, type PortalInput, QuotaExceededError, type RecurringBillingOptions, type RecurringInterval, RecurringReferenceBilling, type RecurringStatus, type RecurringStore, type RecurringSubscription, type RedisLike, RedisUsageStore, type RedisUsageStoreOptions, type RedisWebhookClient, RedisWebhookStore, type RedisWebhookStoreOptions, SUBSCRIPTIONS, StripeBillingGateway, type StripeGatewayOptions, StripeRequestError, type SubscribeInput, type SubscriptionRecord, type SubscriptionStatus, type SubscriptionStore, Subscriptions, type SubscriptionsOptions, type SubscriptionsPluginOptions, type SwapInput, UnknownPlanError, type UsageConsumeResult, type UsageStore, type WebhookEvent, WebhookInvalidError, type WebhookStore, addInterval, assertMinorUnits, billingRoutes, billingWebhookRoute, currencyDecimals, definePlans, featureLimit, formatMoney, isMeter, isMinorUnits, meter, planPrice, subscriptionsPlugin, toMajor, toMinor };
868
+ export { type BillingGateway, type BillingPeriod, type BillingRoutesOptions, type CheckoutInput, type CreateSubscriptionInput, FakeBillingGateway, FakePaymentGateway, FeatureUnavailableError, type FeatureValue, GatewayUnsupportedError, type HandleEventResult, MemoryPaymentStore, MemoryRecurringStore, MemorySubscriptionStore, MemoryUsageStore, MemoryWebhookStore, type Meter, type NewPayment, NotSubscribedError, PaymentAmountMismatchError, type PaymentApplyResult, type PaymentEvent, type PaymentGateway, type PaymentInstruction, PaymentLedger, type PaymentLedgerEvent, type PaymentLedgerEvents, type PaymentLedgerListener, type PaymentLedgerOptions, type PaymentRecord, type PaymentRecordStatus, type PaymentRequest, type PaymentStore, type PlanDefinition, type Plans, type PortalInput, QuotaExceededError, type RecurringBillingOptions, type RecurringInterval, RecurringReferenceBilling, type RecurringStatus, type RecurringStore, type RecurringSubscription, type RedisLike, RedisUsageStore, type RedisUsageStoreOptions, type RedisWebhookClient, RedisWebhookStore, type RedisWebhookStoreOptions, SUBSCRIPTIONS, StripeBillingGateway, type StripeGatewayOptions, StripeRequestError, type SubscribeInput, type SubscriptionRecord, type SubscriptionStatus, type SubscriptionStore, Subscriptions, type SubscriptionsOptions, type SubscriptionsPluginOptions, type SwapInput, UnknownPlanError, type UsageConsumeResult, type UsageStore, type WebhookEvent, WebhookInvalidError, WebhookSecretMissingError, type WebhookStore, addInterval, assertMinorUnits, billingRoutes, billingWebhookRoute, currencyDecimals, definePlans, featureLimit, formatMoney, isMeter, isMinorUnits, meter, planPrice, subscriptionsPlugin, toMajor, toMinor };
package/dist/index.js CHANGED
@@ -160,6 +160,24 @@ var WebhookInvalidError = class extends BasaltError2 {
160
160
  super("BILLING_WEBHOOK_INVALID", "Webhook signature verification failed.");
161
161
  }
162
162
  };
163
+ var WebhookSecretMissingError = class extends BasaltError2 {
164
+ status = 500;
165
+ constructor(gateway) {
166
+ super(
167
+ "BILLING_WEBHOOK_SECRET_MISSING",
168
+ `${gateway}: cannot verify a webhook without a configured signing secret \u2014 refusing to trust an unsigned callback.`
169
+ );
170
+ }
171
+ };
172
+ var PaymentAmountMismatchError = class extends BasaltError2 {
173
+ status = 400;
174
+ constructor(paymentId, expected, actual) {
175
+ super(
176
+ "BILLING_PAYMENT_AMOUNT_MISMATCH",
177
+ `Payment ${paymentId} was requested for ${expected} but the webhook confirmed ${actual} \u2014 refusing to mark it paid.`
178
+ );
179
+ }
180
+ };
163
181
  var FakeBillingGateway = class {
164
182
  name = "fake";
165
183
  created = [];
@@ -303,6 +321,12 @@ var PaymentLedger = class {
303
321
  let record;
304
322
  try {
305
323
  const status = event.type === "payment.succeeded" ? "paid" : "failed";
324
+ if (status === "paid") {
325
+ const existing = await this.store.get(event.paymentId);
326
+ if (existing && existing.amount > 0 && event.amount !== existing.amount) {
327
+ throw new PaymentAmountMismatchError(event.paymentId, existing.amount, event.amount);
328
+ }
329
+ }
306
330
  await this.store.setStatus(event.paymentId, status, {
307
331
  amount: event.amount,
308
332
  ...event.raw !== void 0 ? { raw: event.raw } : {}
@@ -1002,6 +1026,7 @@ export {
1002
1026
  MemoryUsageStore,
1003
1027
  MemoryWebhookStore,
1004
1028
  NotSubscribedError,
1029
+ PaymentAmountMismatchError,
1005
1030
  PaymentLedger,
1006
1031
  QuotaExceededError,
1007
1032
  RecurringReferenceBilling,
@@ -1013,6 +1038,7 @@ export {
1013
1038
  Subscriptions,
1014
1039
  UnknownPlanError,
1015
1040
  WebhookInvalidError,
1041
+ WebhookSecretMissingError,
1016
1042
  addInterval,
1017
1043
  assertMinorUnits,
1018
1044
  billingRoutes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/subscriptions",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Billing for Basalt, Cashier/Soulbscription-style: declarative plans, subscriptions with trials, feature flags, usage limits, gateway drivers and idempotent webhooks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,8 +14,8 @@
14
14
  "dist"
15
15
  ],
16
16
  "dependencies": {
17
- "@basaltkit/fastify": "^1.0.0",
18
- "@basaltkit/core": "^1.0.0"
17
+ "@basaltkit/core": "^1.0.0",
18
+ "@basaltkit/fastify": "^1.1.0"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "zod": "^3.24.0 || ^4.0.0"