@wopr-network/platform-core 0.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/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import { Credit } from "../../credits/credit.js";
|
|
3
|
+
import { chargeAutoTopup } from "../../credits/auto-topup-charge.js";
|
|
4
|
+
import type { IAutoTopupEventLogRepository } from "../../credits/auto-topup-event-log-repository.js";
|
|
5
|
+
import type { ICreditLedger } from "../../credits/credit-ledger.js";
|
|
6
|
+
import {
|
|
7
|
+
type ChargeOpts,
|
|
8
|
+
type ChargeResult,
|
|
9
|
+
type CheckoutOpts,
|
|
10
|
+
type CheckoutSession,
|
|
11
|
+
type Invoice,
|
|
12
|
+
type IPaymentProcessor,
|
|
13
|
+
PaymentMethodOwnershipError,
|
|
14
|
+
type PortalOpts,
|
|
15
|
+
type SavedPaymentMethod,
|
|
16
|
+
type SetupResult,
|
|
17
|
+
type WebhookResult,
|
|
18
|
+
} from "../payment-processor.js";
|
|
19
|
+
import { createCreditCheckoutSession } from "./checkout.js";
|
|
20
|
+
import type { CreditPriceMap } from "./credit-prices.js";
|
|
21
|
+
import { createPortalSession } from "./portal.js";
|
|
22
|
+
import { createSetupIntent } from "./setup-intent.js";
|
|
23
|
+
import type { ITenantCustomerRepository } from "./tenant-store.js";
|
|
24
|
+
|
|
25
|
+
/** Result from the consumer-supplied webhook handler. */
|
|
26
|
+
export interface StripeWebhookHandlerResult {
|
|
27
|
+
handled: boolean;
|
|
28
|
+
event_type: string;
|
|
29
|
+
tenant?: string;
|
|
30
|
+
creditedCents?: number;
|
|
31
|
+
reactivatedBots?: string[];
|
|
32
|
+
duplicate?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StripePaymentProcessorDeps {
|
|
36
|
+
stripe: Stripe;
|
|
37
|
+
tenantRepo: ITenantCustomerRepository;
|
|
38
|
+
webhookSecret: string;
|
|
39
|
+
priceMap?: CreditPriceMap;
|
|
40
|
+
creditLedger: ICreditLedger;
|
|
41
|
+
autoTopupEventLog?: IAutoTopupEventLogRepository;
|
|
42
|
+
/** Consumer-supplied webhook handler (handles domain-specific event processing). */
|
|
43
|
+
webhookHandler?: (event: Stripe.Event) => Promise<StripeWebhookHandlerResult>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class StripePaymentProcessor implements IPaymentProcessor {
|
|
47
|
+
readonly name = "stripe";
|
|
48
|
+
|
|
49
|
+
private readonly stripe: Stripe;
|
|
50
|
+
private readonly tenantRepo: ITenantCustomerRepository;
|
|
51
|
+
private readonly webhookSecret: string;
|
|
52
|
+
private readonly priceMap: CreditPriceMap;
|
|
53
|
+
private readonly creditLedger: ICreditLedger;
|
|
54
|
+
private readonly autoTopupEventLog?: IAutoTopupEventLogRepository;
|
|
55
|
+
private readonly webhookHandler?: (event: Stripe.Event) => Promise<StripeWebhookHandlerResult>;
|
|
56
|
+
|
|
57
|
+
constructor(deps: StripePaymentProcessorDeps) {
|
|
58
|
+
this.stripe = deps.stripe;
|
|
59
|
+
this.tenantRepo = deps.tenantRepo;
|
|
60
|
+
this.webhookSecret = deps.webhookSecret;
|
|
61
|
+
this.priceMap = deps.priceMap ?? new Map();
|
|
62
|
+
this.creditLedger = deps.creditLedger;
|
|
63
|
+
this.autoTopupEventLog = deps.autoTopupEventLog;
|
|
64
|
+
this.webhookHandler = deps.webhookHandler;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async createCheckoutSession(opts: CheckoutOpts): Promise<CheckoutSession> {
|
|
68
|
+
let priceId: string | undefined = opts.priceId;
|
|
69
|
+
|
|
70
|
+
if (!priceId) {
|
|
71
|
+
// Fall back to looking up by amount when no explicit priceId provided.
|
|
72
|
+
const amountCents = opts.amount instanceof Credit ? opts.amount.toCentsFloor() : Number(opts.amount);
|
|
73
|
+
|
|
74
|
+
for (const [id, point] of this.priceMap.entries()) {
|
|
75
|
+
if (point.creditCents === amountCents || point.amountCents === amountCents) {
|
|
76
|
+
priceId = id;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!priceId) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`No Stripe price tier matches amount ${amountCents} cents. Configure STRIPE_CREDIT_PRICE_* env vars.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const session = await createCreditCheckoutSession(
|
|
89
|
+
this.stripe,
|
|
90
|
+
this.tenantRepo as Parameters<typeof createCreditCheckoutSession>[1],
|
|
91
|
+
{
|
|
92
|
+
tenant: opts.tenant,
|
|
93
|
+
priceId,
|
|
94
|
+
successUrl: opts.successUrl,
|
|
95
|
+
cancelUrl: opts.cancelUrl,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: session.id,
|
|
101
|
+
url: session.url ?? "",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async handleWebhook(payload: Buffer, signature: string): Promise<WebhookResult> {
|
|
106
|
+
const event = this.stripe.webhooks.constructEvent(payload, signature, this.webhookSecret);
|
|
107
|
+
|
|
108
|
+
if (!this.webhookHandler) {
|
|
109
|
+
return { handled: false, eventType: event.type };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const result = await this.webhookHandler(event);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
handled: result.handled,
|
|
116
|
+
eventType: result.event_type,
|
|
117
|
+
tenant: result.tenant,
|
|
118
|
+
credited: result.creditedCents != null ? Credit.fromCents(result.creditedCents) : undefined,
|
|
119
|
+
reactivatedBots: result.reactivatedBots,
|
|
120
|
+
duplicate: result.duplicate,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
supportsPortal(): boolean {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async createPortalSession(opts: PortalOpts): Promise<{ url: string }> {
|
|
129
|
+
const session = await createPortalSession(
|
|
130
|
+
this.stripe,
|
|
131
|
+
this.tenantRepo as Parameters<typeof createPortalSession>[1],
|
|
132
|
+
{
|
|
133
|
+
tenant: opts.tenant,
|
|
134
|
+
returnUrl: opts.returnUrl,
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return { url: session.url };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async setupPaymentMethod(tenant: string): Promise<SetupResult> {
|
|
142
|
+
const intent = await createSetupIntent(this.stripe, this.tenantRepo as Parameters<typeof createSetupIntent>[1], {
|
|
143
|
+
tenant,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
clientSecret: intent.client_secret ?? "",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async listPaymentMethods(tenant: string): Promise<SavedPaymentMethod[]> {
|
|
152
|
+
const mapping = await this.tenantRepo.getByTenant(tenant);
|
|
153
|
+
if (!mapping) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const methods = await this.stripe.customers.listPaymentMethods(mapping.processor_customer_id);
|
|
158
|
+
|
|
159
|
+
return methods.data.map((pm, index) => ({
|
|
160
|
+
id: pm.id,
|
|
161
|
+
label: formatPaymentMethodLabel(pm),
|
|
162
|
+
isDefault: index === 0,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async detachPaymentMethod(tenant: string, paymentMethodId: string): Promise<void> {
|
|
167
|
+
const mapping = await this.tenantRepo.getByTenant(tenant);
|
|
168
|
+
if (!mapping) {
|
|
169
|
+
throw new Error(`No Stripe customer found for tenant: ${tenant}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pm = await this.stripe.paymentMethods.retrieve(paymentMethodId);
|
|
173
|
+
if (!pm.customer || pm.customer !== mapping.processor_customer_id) {
|
|
174
|
+
throw new PaymentMethodOwnershipError();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await this.stripe.paymentMethods.detach(paymentMethodId);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async getCustomerEmail(tenantId: string): Promise<string> {
|
|
181
|
+
const mapping = await this.tenantRepo.getByTenant(tenantId);
|
|
182
|
+
if (!mapping) {
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const customer = await this.stripe.customers.retrieve(mapping.processor_customer_id);
|
|
187
|
+
if (customer.deleted) {
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
return customer.email ?? "";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async updateCustomerEmail(tenantId: string, email: string): Promise<void> {
|
|
194
|
+
const mapping = await this.tenantRepo.getByTenant(tenantId);
|
|
195
|
+
if (!mapping) {
|
|
196
|
+
throw new Error(`No Stripe customer found for tenant: ${tenantId}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await this.stripe.customers.update(mapping.processor_customer_id, { email });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async listInvoices(tenantId: string): Promise<Invoice[]> {
|
|
203
|
+
const mapping = await this.tenantRepo.getByTenant(tenantId);
|
|
204
|
+
if (!mapping) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const invoices = await this.stripe.invoices.list({
|
|
209
|
+
customer: mapping.processor_customer_id,
|
|
210
|
+
limit: 24,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return invoices.data.map((inv) => ({
|
|
214
|
+
id: inv.id,
|
|
215
|
+
date: new Date(inv.created * 1000).toISOString(),
|
|
216
|
+
amountCents: inv.amount_due,
|
|
217
|
+
status: inv.status ?? "unknown",
|
|
218
|
+
downloadUrl: inv.invoice_pdf ?? "",
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async charge(opts: ChargeOpts): Promise<ChargeResult> {
|
|
223
|
+
if (!this.autoTopupEventLog) {
|
|
224
|
+
throw new Error("autoTopupEventLog is required for charge()");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const amount = opts.amount instanceof Credit ? opts.amount : Credit.fromCents(Number(opts.amount));
|
|
228
|
+
|
|
229
|
+
const result = await chargeAutoTopup(
|
|
230
|
+
{
|
|
231
|
+
stripe: this.stripe,
|
|
232
|
+
tenantRepo: this.tenantRepo,
|
|
233
|
+
creditLedger: this.creditLedger,
|
|
234
|
+
eventLogRepo: this.autoTopupEventLog,
|
|
235
|
+
},
|
|
236
|
+
opts.tenant,
|
|
237
|
+
amount,
|
|
238
|
+
opts.source,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: result.success,
|
|
243
|
+
paymentReference: result.paymentReference,
|
|
244
|
+
error: result.error,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function formatPaymentMethodLabel(pm: Stripe.PaymentMethod): string {
|
|
250
|
+
if (pm.card) {
|
|
251
|
+
const brand = pm.card.brand.charAt(0).toUpperCase() + pm.card.brand.slice(1);
|
|
252
|
+
return `${brand} ending ${pm.card.last4}`;
|
|
253
|
+
}
|
|
254
|
+
return `Payment method ${pm.id}`;
|
|
255
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import type { PlatformDb } from "../../db/index.js";
|
|
4
|
+
import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../../test/db.js";
|
|
5
|
+
import { TenantCustomerRepository } from "./tenant-store.js";
|
|
6
|
+
|
|
7
|
+
describe("TenantCustomerRepository", () => {
|
|
8
|
+
let pool: PGlite;
|
|
9
|
+
let db: PlatformDb;
|
|
10
|
+
let store: TenantCustomerRepository;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
({ db, pool } = await createTestDb());
|
|
14
|
+
await beginTestTransaction(pool);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await endTestTransaction(pool);
|
|
19
|
+
await pool.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
await rollbackTestTransaction(pool);
|
|
24
|
+
store = new TenantCustomerRepository(db);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("upsert and getByTenant", () => {
|
|
28
|
+
it("inserts a new tenant mapping", async () => {
|
|
29
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_abc" });
|
|
30
|
+
const row = await store.getByTenant("t-1");
|
|
31
|
+
|
|
32
|
+
expect(row).not.toBeNull();
|
|
33
|
+
expect(row?.tenant).toBe("t-1");
|
|
34
|
+
expect(row?.processor_customer_id).toBe("cus_abc");
|
|
35
|
+
expect(row?.tier).toBe("free");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("updates existing mapping on conflict", async () => {
|
|
39
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_old" });
|
|
40
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_new" });
|
|
41
|
+
|
|
42
|
+
const row = await store.getByTenant("t-1");
|
|
43
|
+
expect(row?.processor_customer_id).toBe("cus_new");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns null for non-existent tenant", async () => {
|
|
47
|
+
expect(await store.getByTenant("nonexistent")).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("getByProcessorCustomerId", () => {
|
|
52
|
+
it("looks up by processor customer ID", async () => {
|
|
53
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_lookup" });
|
|
54
|
+
const row = await store.getByProcessorCustomerId("cus_lookup");
|
|
55
|
+
|
|
56
|
+
expect(row).not.toBeNull();
|
|
57
|
+
expect(row?.tenant).toBe("t-1");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns null for unknown processor customer", async () => {
|
|
61
|
+
expect(await store.getByProcessorCustomerId("cus_unknown")).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("setTier", () => {
|
|
66
|
+
it("updates the tier for a tenant", async () => {
|
|
67
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_abc" });
|
|
68
|
+
await store.setTier("t-1", "enterprise");
|
|
69
|
+
|
|
70
|
+
const row = await store.getByTenant("t-1");
|
|
71
|
+
expect(row?.tier).toBe("enterprise");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("setBillingHold and hasBillingHold", () => {
|
|
76
|
+
it("sets and checks billing hold", async () => {
|
|
77
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_abc" });
|
|
78
|
+
|
|
79
|
+
expect(await store.hasBillingHold("t-1")).toBe(false);
|
|
80
|
+
|
|
81
|
+
await store.setBillingHold("t-1", true);
|
|
82
|
+
expect(await store.hasBillingHold("t-1")).toBe(true);
|
|
83
|
+
|
|
84
|
+
await store.setBillingHold("t-1", false);
|
|
85
|
+
expect(await store.hasBillingHold("t-1")).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns false for non-existent tenant", async () => {
|
|
89
|
+
expect(await store.hasBillingHold("nonexistent")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("list", () => {
|
|
94
|
+
it("returns all tenant mappings", async () => {
|
|
95
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_1" });
|
|
96
|
+
await store.upsert({ tenant: "t-2", processorCustomerId: "cus_2" });
|
|
97
|
+
await store.upsert({ tenant: "t-3", processorCustomerId: "cus_3" });
|
|
98
|
+
|
|
99
|
+
const rows = await store.list();
|
|
100
|
+
expect(rows).toHaveLength(3);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns empty array when no tenants exist", async () => {
|
|
104
|
+
expect(await store.list()).toHaveLength(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("buildCustomerIdMap", () => {
|
|
109
|
+
it("builds a tenant->processorCustomerId map", async () => {
|
|
110
|
+
await store.upsert({ tenant: "t-1", processorCustomerId: "cus_1" });
|
|
111
|
+
await store.upsert({ tenant: "t-2", processorCustomerId: "cus_2" });
|
|
112
|
+
|
|
113
|
+
const map = await store.buildCustomerIdMap();
|
|
114
|
+
expect(map).toEqual({
|
|
115
|
+
"t-1": "cus_1",
|
|
116
|
+
"t-2": "cus_2",
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("returns empty object when no tenants exist", async () => {
|
|
121
|
+
expect(await store.buildCustomerIdMap()).toEqual({});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { desc, eq } from "drizzle-orm";
|
|
2
|
+
import type { PlatformDb } from "../../db/index.js";
|
|
3
|
+
import { tenantCustomers } from "../../db/schema/tenant-customers.js";
|
|
4
|
+
import type { TenantCustomerRow } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export interface ITenantCustomerRepository {
|
|
7
|
+
getByTenant(tenant: string): Promise<TenantCustomerRow | null>;
|
|
8
|
+
getByProcessorCustomerId(processorCustomerId: string): Promise<TenantCustomerRow | null>;
|
|
9
|
+
upsert(row: { tenant: string; processorCustomerId: string; tier?: string }): Promise<void>;
|
|
10
|
+
setTier(tenant: string, tier: string): Promise<void>;
|
|
11
|
+
setBillingHold(tenant: string, hold: boolean): Promise<void>;
|
|
12
|
+
hasBillingHold(tenant: string): Promise<boolean>;
|
|
13
|
+
getInferenceMode(tenant: string): Promise<string>;
|
|
14
|
+
setInferenceMode(tenant: string, mode: string): Promise<void>;
|
|
15
|
+
list(): Promise<TenantCustomerRow[]>;
|
|
16
|
+
buildCustomerIdMap(): Promise<Record<string, string>>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Manages tenant-to-payment-processor customer mappings in PostgreSQL.
|
|
21
|
+
*
|
|
22
|
+
* This is the bridge between WOPR tenant IDs and processor customer IDs.
|
|
23
|
+
* All billing operations look up the processor customer via this store.
|
|
24
|
+
*
|
|
25
|
+
* Note: No subscription tracking — WOPR uses credits, not subscriptions.
|
|
26
|
+
* Credit balances are managed by ICreditLedger / DrizzleCreditLedger.
|
|
27
|
+
*/
|
|
28
|
+
export class DrizzleTenantCustomerRepository implements ITenantCustomerRepository {
|
|
29
|
+
constructor(private readonly db: PlatformDb) {}
|
|
30
|
+
|
|
31
|
+
/** Get a tenant's processor mapping. */
|
|
32
|
+
async getByTenant(tenant: string): Promise<TenantCustomerRow | null> {
|
|
33
|
+
const row = (await this.db.select().from(tenantCustomers).where(eq(tenantCustomers.tenant, tenant)))[0];
|
|
34
|
+
return row ? mapRow(row) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Get a tenant mapping by processor customer ID. */
|
|
38
|
+
async getByProcessorCustomerId(processorCustomerId: string): Promise<TenantCustomerRow | null> {
|
|
39
|
+
const row = (
|
|
40
|
+
await this.db.select().from(tenantCustomers).where(eq(tenantCustomers.processorCustomerId, processorCustomerId))
|
|
41
|
+
)[0];
|
|
42
|
+
return row ? mapRow(row) : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Upsert a tenant-to-customer mapping. */
|
|
46
|
+
async upsert(row: { tenant: string; processorCustomerId: string; tier?: string }): Promise<void> {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
await this.db
|
|
49
|
+
.insert(tenantCustomers)
|
|
50
|
+
.values({
|
|
51
|
+
tenant: row.tenant,
|
|
52
|
+
processorCustomerId: row.processorCustomerId,
|
|
53
|
+
tier: row.tier ?? "free",
|
|
54
|
+
createdAt: now,
|
|
55
|
+
updatedAt: now,
|
|
56
|
+
})
|
|
57
|
+
.onConflictDoUpdate({
|
|
58
|
+
target: tenantCustomers.tenant,
|
|
59
|
+
set: {
|
|
60
|
+
processorCustomerId: row.processorCustomerId,
|
|
61
|
+
tier: row.tier !== undefined ? row.tier : undefined,
|
|
62
|
+
updatedAt: now,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Update the tier for a tenant. */
|
|
68
|
+
async setTier(tenant: string, tier: string): Promise<void> {
|
|
69
|
+
await this.db
|
|
70
|
+
.update(tenantCustomers)
|
|
71
|
+
.set({ tier, updatedAt: Date.now() })
|
|
72
|
+
.where(eq(tenantCustomers.tenant, tenant));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Set or clear the billing hold flag for a tenant. */
|
|
76
|
+
async setBillingHold(tenant: string, hold: boolean): Promise<void> {
|
|
77
|
+
await this.db
|
|
78
|
+
.update(tenantCustomers)
|
|
79
|
+
.set({ billingHold: hold ? 1 : 0, updatedAt: Date.now() })
|
|
80
|
+
.where(eq(tenantCustomers.tenant, tenant));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Check whether a tenant has an active billing hold. */
|
|
84
|
+
async hasBillingHold(tenant: string): Promise<boolean> {
|
|
85
|
+
const row = (
|
|
86
|
+
await this.db
|
|
87
|
+
.select({ billingHold: tenantCustomers.billingHold })
|
|
88
|
+
.from(tenantCustomers)
|
|
89
|
+
.where(eq(tenantCustomers.tenant, tenant))
|
|
90
|
+
)[0];
|
|
91
|
+
return row?.billingHold === 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Get inference mode for a tenant (defaults to "byok"). */
|
|
95
|
+
async getInferenceMode(tenant: string): Promise<string> {
|
|
96
|
+
const row = (
|
|
97
|
+
await this.db
|
|
98
|
+
.select({ inferenceMode: tenantCustomers.inferenceMode })
|
|
99
|
+
.from(tenantCustomers)
|
|
100
|
+
.where(eq(tenantCustomers.tenant, tenant))
|
|
101
|
+
)[0];
|
|
102
|
+
return row?.inferenceMode ?? "byok";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Set inference mode for a tenant. */
|
|
106
|
+
async setInferenceMode(tenant: string, mode: string): Promise<void> {
|
|
107
|
+
await this.db
|
|
108
|
+
.update(tenantCustomers)
|
|
109
|
+
.set({ inferenceMode: mode, updatedAt: Date.now() })
|
|
110
|
+
.where(eq(tenantCustomers.tenant, tenant));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** List all tenants with processor mappings. */
|
|
114
|
+
async list(): Promise<TenantCustomerRow[]> {
|
|
115
|
+
const rows = await this.db.select().from(tenantCustomers).orderBy(desc(tenantCustomers.createdAt));
|
|
116
|
+
return rows.map(mapRow);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Build a tenant -> processor_customer_id map for use with UsageAggregationWorker. */
|
|
120
|
+
async buildCustomerIdMap(): Promise<Record<string, string>> {
|
|
121
|
+
const rows = await this.db
|
|
122
|
+
.select({
|
|
123
|
+
tenant: tenantCustomers.tenant,
|
|
124
|
+
processorCustomerId: tenantCustomers.processorCustomerId,
|
|
125
|
+
})
|
|
126
|
+
.from(tenantCustomers);
|
|
127
|
+
|
|
128
|
+
const map: Record<string, string> = {};
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
map[row.tenant] = row.processorCustomerId;
|
|
131
|
+
}
|
|
132
|
+
return map;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Map a Drizzle row to the TenantCustomerRow interface (snake_case field names). */
|
|
137
|
+
function mapRow(row: typeof tenantCustomers.$inferSelect): TenantCustomerRow {
|
|
138
|
+
return {
|
|
139
|
+
tenant: row.tenant,
|
|
140
|
+
processor_customer_id: row.processorCustomerId,
|
|
141
|
+
processor: row.processor,
|
|
142
|
+
tier: row.tier,
|
|
143
|
+
billing_hold: row.billingHold,
|
|
144
|
+
inference_mode: row.inferenceMode,
|
|
145
|
+
created_at: row.createdAt,
|
|
146
|
+
updated_at: row.updatedAt,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Backward-compat alias.
|
|
151
|
+
export { DrizzleTenantCustomerRepository as TenantCustomerRepository };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Tenant-to-payment-processor customer mapping stored in SQLite. */
|
|
2
|
+
export interface TenantCustomerRow {
|
|
3
|
+
tenant: string;
|
|
4
|
+
processor_customer_id: string;
|
|
5
|
+
processor: string;
|
|
6
|
+
tier: string;
|
|
7
|
+
billing_hold: number;
|
|
8
|
+
inference_mode: string;
|
|
9
|
+
created_at: number;
|
|
10
|
+
updated_at: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Options for creating a Stripe credit purchase Checkout session. */
|
|
14
|
+
export interface CreditCheckoutOpts {
|
|
15
|
+
/** Internal tenant ID. */
|
|
16
|
+
tenant: string;
|
|
17
|
+
/** Stripe Price ID for the credit purchase. */
|
|
18
|
+
priceId: string;
|
|
19
|
+
/** URL to redirect to after successful checkout. */
|
|
20
|
+
successUrl: string;
|
|
21
|
+
/** URL to redirect to if the user cancels checkout. */
|
|
22
|
+
cancelUrl: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options for creating a Stripe Customer Portal session. */
|
|
26
|
+
export interface PortalSessionOpts {
|
|
27
|
+
/** Internal tenant ID (used to look up Stripe customer). */
|
|
28
|
+
tenant: string;
|
|
29
|
+
/** URL to redirect to when the user is done managing billing. */
|
|
30
|
+
returnUrl: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Options for creating a Stripe VPS subscription Checkout session. */
|
|
34
|
+
export interface VpsCheckoutOpts {
|
|
35
|
+
/** Internal tenant ID. */
|
|
36
|
+
tenant: string;
|
|
37
|
+
/** Bot instance ID being upgraded. */
|
|
38
|
+
botId: string;
|
|
39
|
+
/** Stripe Price ID for the VPS subscription. */
|
|
40
|
+
vpsPriceId: string;
|
|
41
|
+
/** URL to redirect to after successful checkout. */
|
|
42
|
+
successUrl: string;
|
|
43
|
+
/** URL to redirect to if the user cancels checkout. */
|
|
44
|
+
cancelUrl: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Configuration for the Stripe billing integration. */
|
|
48
|
+
export interface StripeBillingConfig {
|
|
49
|
+
/** Stripe secret API key. */
|
|
50
|
+
secretKey: string;
|
|
51
|
+
/** Stripe webhook signing secret. */
|
|
52
|
+
webhookSecret: string;
|
|
53
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WebhookSeenEvent } from "../credits/repository-types.js";
|
|
2
|
+
|
|
3
|
+
export interface IWebhookSeenRepository {
|
|
4
|
+
/** Returns true if the event ID + source combination has already been seen. */
|
|
5
|
+
isDuplicate(eventId: string, source: string): Promise<boolean>;
|
|
6
|
+
/** Mark an event ID + source as seen. */
|
|
7
|
+
markSeen(eventId: string, source: string): Promise<WebhookSeenEvent>;
|
|
8
|
+
/** Delete entries older than ttlMs. Returns count of deleted rows. */
|
|
9
|
+
purgeExpired(ttlMs: number): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* No-op replay guard that never reports duplicates.
|
|
14
|
+
* Use in tests that don't need deduplication behaviour.
|
|
15
|
+
*/
|
|
16
|
+
export const noOpReplayGuard: IWebhookSeenRepository = {
|
|
17
|
+
isDuplicate: async () => false,
|
|
18
|
+
markSeen: async (eventId: string, source: string): Promise<WebhookSeenEvent> => ({
|
|
19
|
+
eventId,
|
|
20
|
+
source,
|
|
21
|
+
seenAt: Date.now(),
|
|
22
|
+
}),
|
|
23
|
+
purgeExpired: async () => 0,
|
|
24
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
// We test the schema directly, not the singleton config export,
|
|
4
|
+
// because the singleton is already parsed at import time.
|
|
5
|
+
// Instead we test the schema shape by importing and re-parsing.
|
|
6
|
+
|
|
7
|
+
describe("billing env validation", () => {
|
|
8
|
+
it("uses correct defaults when no env vars set", async () => {
|
|
9
|
+
// Dynamic import to test schema defaults
|
|
10
|
+
const { billingConfigSchema } = await import("./index.js");
|
|
11
|
+
const result = billingConfigSchema.parse({});
|
|
12
|
+
expect(result).toEqual({
|
|
13
|
+
affiliateMatchRate: 1.0,
|
|
14
|
+
affiliateMaxReferrals30d: 20,
|
|
15
|
+
affiliateMaxMatchCredits30d: 20000,
|
|
16
|
+
affiliateNewUserBonusRate: 0.2,
|
|
17
|
+
dividendMatchRate: 1.0,
|
|
18
|
+
meterMaxRetries: 3,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("coerces valid string values to numbers", async () => {
|
|
23
|
+
const { billingConfigSchema } = await import("./index.js");
|
|
24
|
+
const result = billingConfigSchema.parse({
|
|
25
|
+
affiliateMatchRate: "0.5",
|
|
26
|
+
affiliateMaxReferrals30d: "10",
|
|
27
|
+
affiliateMaxMatchCredits30d: "5000",
|
|
28
|
+
affiliateNewUserBonusRate: "0.15",
|
|
29
|
+
dividendMatchRate: "0.75",
|
|
30
|
+
meterMaxRetries: "5",
|
|
31
|
+
});
|
|
32
|
+
expect(result.affiliateMatchRate).toBe(0.5);
|
|
33
|
+
expect(result.affiliateMaxReferrals30d).toBe(10);
|
|
34
|
+
expect(result.affiliateMaxMatchCredits30d).toBe(5000);
|
|
35
|
+
expect(result.affiliateNewUserBonusRate).toBe(0.15);
|
|
36
|
+
expect(result.dividendMatchRate).toBe(0.75);
|
|
37
|
+
expect(result.meterMaxRetries).toBe(5);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("rejects non-numeric strings", async () => {
|
|
41
|
+
const { billingConfigSchema } = await import("./index.js");
|
|
42
|
+
expect(() => billingConfigSchema.parse({ affiliateMatchRate: "not-a-number" })).toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("rejects negative rates", async () => {
|
|
46
|
+
const { billingConfigSchema } = await import("./index.js");
|
|
47
|
+
expect(() => billingConfigSchema.parse({ affiliateMatchRate: "-0.5" })).toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("rejects non-integer retries", async () => {
|
|
51
|
+
const { billingConfigSchema } = await import("./index.js");
|
|
52
|
+
expect(() => billingConfigSchema.parse({ meterMaxRetries: "2.5" })).toThrow();
|
|
53
|
+
});
|
|
54
|
+
});
|