@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,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for PayRam webhook handler (WOP-407).
|
|
3
|
+
*
|
|
4
|
+
* Covers FILLED/OVER_FILLED crediting the ledger, PARTIALLY_FILLED/CANCELLED
|
|
5
|
+
* no-op status, idempotency, replay guard, and bot reactivation.
|
|
6
|
+
*/
|
|
7
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { createTestDb, truncateAllTables } from "../../test/db.js";
|
|
9
|
+
import { CreditLedger } from "../../credits/credit-ledger.js";
|
|
10
|
+
import { DrizzleWebhookSeenRepository } from "../drizzle-webhook-seen-repository.js";
|
|
11
|
+
import { noOpReplayGuard } from "../webhook-seen-repository.js";
|
|
12
|
+
import { PayRamChargeRepository } from "./charge-store.js";
|
|
13
|
+
import { handlePayRamWebhook } from "./webhook.js";
|
|
14
|
+
function makePayload(overrides = {}) {
|
|
15
|
+
return {
|
|
16
|
+
reference_id: "ref-test-001",
|
|
17
|
+
status: "FILLED",
|
|
18
|
+
amount: "25.00",
|
|
19
|
+
currency: "USDC",
|
|
20
|
+
filled_amount: "25.00",
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// TOP OF FILE - shared across ALL describes
|
|
25
|
+
let pool;
|
|
26
|
+
let db;
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
({ db, pool } = await createTestDb());
|
|
29
|
+
});
|
|
30
|
+
afterAll(async () => {
|
|
31
|
+
await pool.close();
|
|
32
|
+
});
|
|
33
|
+
describe("handlePayRamWebhook", () => {
|
|
34
|
+
let chargeStore;
|
|
35
|
+
let creditLedger;
|
|
36
|
+
let deps;
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
await truncateAllTables(pool);
|
|
39
|
+
chargeStore = new PayRamChargeRepository(db);
|
|
40
|
+
creditLedger = new CreditLedger(db);
|
|
41
|
+
deps = { chargeStore, creditLedger, replayGuard: noOpReplayGuard };
|
|
42
|
+
// Create a default test charge
|
|
43
|
+
await chargeStore.create("ref-test-001", "tenant-a", 2500);
|
|
44
|
+
});
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// FILLED / OVER_FILLED — should credit ledger
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
describe("FILLED status", () => {
|
|
49
|
+
it("credits the ledger with the requested USD amount", async () => {
|
|
50
|
+
const result = await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
51
|
+
expect(result.handled).toBe(true);
|
|
52
|
+
expect(result.status).toBe("FILLED");
|
|
53
|
+
expect(result.tenant).toBe("tenant-a");
|
|
54
|
+
expect(result.creditedCents).toBe(2500);
|
|
55
|
+
const balance = await creditLedger.balance("tenant-a");
|
|
56
|
+
expect(balance.toCents()).toBe(2500);
|
|
57
|
+
});
|
|
58
|
+
it("uses payram: prefix on reference ID in credit transaction", async () => {
|
|
59
|
+
await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
60
|
+
const history = await creditLedger.history("tenant-a");
|
|
61
|
+
expect(history).toHaveLength(1);
|
|
62
|
+
expect(history[0].referenceId).toBe("payram:ref-test-001");
|
|
63
|
+
expect(history[0].type).toBe("purchase");
|
|
64
|
+
});
|
|
65
|
+
it("records fundingSource as payram", async () => {
|
|
66
|
+
await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
67
|
+
const history = await creditLedger.history("tenant-a");
|
|
68
|
+
expect(history[0].fundingSource).toBe("payram");
|
|
69
|
+
});
|
|
70
|
+
it("marks the charge as credited after FILLED", async () => {
|
|
71
|
+
await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
72
|
+
expect(await chargeStore.isCredited("ref-test-001")).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("is idempotent — duplicate FILLED webhook does not double-credit", async () => {
|
|
75
|
+
await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
76
|
+
const result2 = await handlePayRamWebhook(deps, makePayload({ status: "FILLED" }));
|
|
77
|
+
expect(result2.handled).toBe(true);
|
|
78
|
+
expect(result2.creditedCents).toBe(0);
|
|
79
|
+
const balance = await creditLedger.balance("tenant-a");
|
|
80
|
+
expect(balance.toCents()).toBe(2500); // Only credited once
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("OVER_FILLED status", () => {
|
|
84
|
+
it("credits the requested USD amount (not the overpayment)", async () => {
|
|
85
|
+
await chargeStore.create("ref-over-001", "tenant-b", 1000);
|
|
86
|
+
const result = await handlePayRamWebhook(deps, makePayload({
|
|
87
|
+
reference_id: "ref-over-001",
|
|
88
|
+
status: "OVER_FILLED",
|
|
89
|
+
filled_amount: "12.50", // Overpaid by $2.50
|
|
90
|
+
currency: "ETH",
|
|
91
|
+
}));
|
|
92
|
+
expect(result.handled).toBe(true);
|
|
93
|
+
expect(result.creditedCents).toBe(1000); // Only the requested amount
|
|
94
|
+
expect((await creditLedger.balance("tenant-b")).toCents()).toBe(1000);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Statuses that should NOT credit the ledger
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
describe("PARTIALLY_FILLED status", () => {
|
|
101
|
+
it("does NOT credit the ledger", async () => {
|
|
102
|
+
const result = await handlePayRamWebhook(deps, makePayload({ status: "PARTIALLY_FILLED" }));
|
|
103
|
+
expect(result.handled).toBe(true);
|
|
104
|
+
expect(result.tenant).toBe("tenant-a");
|
|
105
|
+
expect(result.creditedCents).toBeUndefined();
|
|
106
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe("VERIFYING status", () => {
|
|
110
|
+
it("does NOT credit the ledger", async () => {
|
|
111
|
+
const result = await handlePayRamWebhook(deps, makePayload({ status: "VERIFYING" }));
|
|
112
|
+
expect(result.handled).toBe(true);
|
|
113
|
+
expect(result.creditedCents).toBeUndefined();
|
|
114
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("OPEN status", () => {
|
|
118
|
+
it("does NOT credit the ledger", async () => {
|
|
119
|
+
const result = await handlePayRamWebhook(deps, makePayload({ status: "OPEN" }));
|
|
120
|
+
expect(result.handled).toBe(true);
|
|
121
|
+
expect(result.creditedCents).toBeUndefined();
|
|
122
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("CANCELLED status", () => {
|
|
126
|
+
it("does NOT credit the ledger", async () => {
|
|
127
|
+
const result = await handlePayRamWebhook(deps, makePayload({ status: "CANCELLED" }));
|
|
128
|
+
expect(result.handled).toBe(true);
|
|
129
|
+
expect(result.creditedCents).toBeUndefined();
|
|
130
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(0);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Unknown reference ID
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
describe("unknown reference_id", () => {
|
|
137
|
+
it("returns handled:false when charge not found", async () => {
|
|
138
|
+
const result = await handlePayRamWebhook(deps, makePayload({ reference_id: "ref-unknown-999" }));
|
|
139
|
+
expect(result.handled).toBe(false);
|
|
140
|
+
expect(result.tenant).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Charge store updates
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
describe("charge store updates", () => {
|
|
147
|
+
it("updates charge status on every webhook call", async () => {
|
|
148
|
+
await handlePayRamWebhook(deps, makePayload({ status: "VERIFYING" }));
|
|
149
|
+
const charge = await chargeStore.getByReferenceId("ref-test-001");
|
|
150
|
+
expect(charge?.status).toBe("VERIFYING");
|
|
151
|
+
});
|
|
152
|
+
it("updates currency and filled_amount on FILLED", async () => {
|
|
153
|
+
await handlePayRamWebhook(deps, makePayload({ status: "FILLED", currency: "USDT", filled_amount: "25.00" }));
|
|
154
|
+
const charge = await chargeStore.getByReferenceId("ref-test-001");
|
|
155
|
+
expect(charge?.currency).toBe("USDT");
|
|
156
|
+
expect(charge?.filledAmount).toBe("25.00");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Multiple tenants / reference IDs
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
describe("different reference IDs", () => {
|
|
163
|
+
it("processes multiple reference IDs independently", async () => {
|
|
164
|
+
await chargeStore.create("ref-b-001", "tenant-b", 5000);
|
|
165
|
+
await chargeStore.create("ref-c-001", "tenant-c", 1500);
|
|
166
|
+
await handlePayRamWebhook(deps, makePayload({ reference_id: "ref-b-001", status: "FILLED" }));
|
|
167
|
+
await handlePayRamWebhook(deps, makePayload({ reference_id: "ref-c-001", status: "FILLED" }));
|
|
168
|
+
expect((await creditLedger.balance("tenant-b")).toCents()).toBe(5000);
|
|
169
|
+
expect((await creditLedger.balance("tenant-c")).toCents()).toBe(1500);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Replay guard
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
describe("replay guard", () => {
|
|
176
|
+
it("blocks duplicate reference_id + status combos", async () => {
|
|
177
|
+
const replayGuard = new DrizzleWebhookSeenRepository(db);
|
|
178
|
+
const depsWithGuard = { ...deps, replayGuard };
|
|
179
|
+
const first = await handlePayRamWebhook(depsWithGuard, makePayload({ status: "FILLED" }));
|
|
180
|
+
expect(first.handled).toBe(true);
|
|
181
|
+
expect(first.creditedCents).toBe(2500);
|
|
182
|
+
expect(first.duplicate).toBeUndefined();
|
|
183
|
+
const second = await handlePayRamWebhook(depsWithGuard, makePayload({ status: "FILLED" }));
|
|
184
|
+
expect(second.handled).toBe(true);
|
|
185
|
+
expect(second.duplicate).toBe(true);
|
|
186
|
+
expect(second.creditedCents).toBeUndefined();
|
|
187
|
+
// Only credited once
|
|
188
|
+
expect((await creditLedger.balance("tenant-a")).toCents()).toBe(2500);
|
|
189
|
+
});
|
|
190
|
+
it("same reference_id with different status is not blocked by replay guard", async () => {
|
|
191
|
+
const replayGuard = new DrizzleWebhookSeenRepository(db);
|
|
192
|
+
const depsWithGuard = { ...deps, replayGuard };
|
|
193
|
+
await handlePayRamWebhook(depsWithGuard, makePayload({ status: "VERIFYING" }));
|
|
194
|
+
const result = await handlePayRamWebhook(depsWithGuard, makePayload({ status: "FILLED" }));
|
|
195
|
+
expect(result.duplicate).toBeUndefined();
|
|
196
|
+
expect(result.creditedCents).toBe(2500);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Bot reactivation
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
describe("resource reactivation via onCreditsPurchased", () => {
|
|
203
|
+
it("calls onCreditsPurchased on FILLED and includes reactivatedBots in result", async () => {
|
|
204
|
+
const mockOnCreditsPurchased = vi.fn().mockResolvedValue(["bot-1", "bot-2"]);
|
|
205
|
+
const depsWithCallback = {
|
|
206
|
+
...deps,
|
|
207
|
+
onCreditsPurchased: mockOnCreditsPurchased,
|
|
208
|
+
};
|
|
209
|
+
const result = await handlePayRamWebhook(depsWithCallback, makePayload({ status: "FILLED" }));
|
|
210
|
+
expect(mockOnCreditsPurchased).toHaveBeenCalledWith("tenant-a", creditLedger);
|
|
211
|
+
expect(result.reactivatedBots).toEqual(["bot-1", "bot-2"]);
|
|
212
|
+
});
|
|
213
|
+
it("does not include reactivatedBots when no resources reactivated", async () => {
|
|
214
|
+
const mockOnCreditsPurchased = vi.fn().mockResolvedValue([]);
|
|
215
|
+
const depsWithCallback = {
|
|
216
|
+
...deps,
|
|
217
|
+
onCreditsPurchased: mockOnCreditsPurchased,
|
|
218
|
+
};
|
|
219
|
+
const result = await handlePayRamWebhook(depsWithCallback, makePayload({ status: "FILLED" }));
|
|
220
|
+
expect(result.reactivatedBots).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// DrizzleWebhookSeenRepository unit tests (replaces PayRamReplayGuard)
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
describe("DrizzleWebhookSeenRepository (payram replay guard)", () => {
|
|
228
|
+
beforeEach(async () => {
|
|
229
|
+
await truncateAllTables(pool);
|
|
230
|
+
});
|
|
231
|
+
it("reports unseen keys as not duplicate", async () => {
|
|
232
|
+
const guard = new DrizzleWebhookSeenRepository(db);
|
|
233
|
+
expect(await guard.isDuplicate("ref-001:FILLED", "payram")).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
it("reports seen keys as duplicate", async () => {
|
|
236
|
+
const guard = new DrizzleWebhookSeenRepository(db);
|
|
237
|
+
await guard.markSeen("ref-001:FILLED", "payram");
|
|
238
|
+
expect(await guard.isDuplicate("ref-001:FILLED", "payram")).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
it("purges expired entries via purgeExpired", async () => {
|
|
241
|
+
const guard = new DrizzleWebhookSeenRepository(db);
|
|
242
|
+
await guard.markSeen("ref-expire:FILLED", "payram");
|
|
243
|
+
expect(await guard.isDuplicate("ref-expire:FILLED", "payram")).toBe(true);
|
|
244
|
+
// Negative TTL pushes cutoff into the future — entry is expired
|
|
245
|
+
await guard.purgeExpired(-24 * 60 * 60 * 1000);
|
|
246
|
+
expect(await guard.isDuplicate("ref-expire:FILLED", "payram")).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { CREDIT_PRICE_POINTS, getCreditAmountForPurchase } from "./credit-prices.js";
|
|
3
|
+
/**
|
|
4
|
+
* Regression tests for the cents/credits boundary (WOP-1058).
|
|
5
|
+
*
|
|
6
|
+
* These tests verify that:
|
|
7
|
+
* 1. CreditPricePoint.amountCents values are valid USD cent amounts (not nanodollars)
|
|
8
|
+
* 2. CreditPricePoint.creditCents values are >= amountCents (bonus never negative)
|
|
9
|
+
* 3. getCreditAmountForPurchase returns creditCents (not some other unit)
|
|
10
|
+
* 4. No price point value approaches nanodollar scale (SCALE = 1_000_000_000)
|
|
11
|
+
*
|
|
12
|
+
* If any of these tests fail after a rename/refactor, a _cents field was
|
|
13
|
+
* incorrectly changed to store Credit raw units (nanodollars) instead of
|
|
14
|
+
* USD cents. See src/monetization/credits/credit-ledger.ts for naming convention.
|
|
15
|
+
*/
|
|
16
|
+
describe("WOP-1058: Stripe cents/credits boundary", () => {
|
|
17
|
+
it("all amountCents values are positive integers representing USD cents", () => {
|
|
18
|
+
for (const point of CREDIT_PRICE_POINTS) {
|
|
19
|
+
expect(point.amountCents).toBeGreaterThan(0);
|
|
20
|
+
expect(Number.isInteger(point.amountCents)).toBe(true);
|
|
21
|
+
// Sanity: no tier charges more than $1000 (100_000 cents)
|
|
22
|
+
expect(point.amountCents).toBeLessThanOrEqual(100_000);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
it("amountCents values are never in nanodollar range (would indicate unit confusion)", () => {
|
|
26
|
+
for (const point of CREDIT_PRICE_POINTS) {
|
|
27
|
+
// Credit.SCALE = 1_000_000_000. If a value is in this range,
|
|
28
|
+
// a _cents field was mistakenly assigned a Credit.toRaw() value.
|
|
29
|
+
expect(point.amountCents).toBeLessThan(1_000_000);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
it("all creditCents values are >= amountCents (bonus is non-negative)", () => {
|
|
33
|
+
for (const point of CREDIT_PRICE_POINTS) {
|
|
34
|
+
expect(point.creditCents).toBeGreaterThanOrEqual(point.amountCents);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it("creditCents values are never in nanodollar range (would indicate unit confusion)", () => {
|
|
38
|
+
for (const point of CREDIT_PRICE_POINTS) {
|
|
39
|
+
expect(point.creditCents).toBeLessThan(1_000_000);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it("getCreditAmountForPurchase returns creditCents for known tiers", () => {
|
|
43
|
+
for (const point of CREDIT_PRICE_POINTS) {
|
|
44
|
+
const result = getCreditAmountForPurchase(point.amountCents);
|
|
45
|
+
expect(result).toBe(point.creditCents);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
it("getCreditAmountForPurchase returns input for unknown amounts (1:1 fallback)", () => {
|
|
49
|
+
const unknownAmount = 1234;
|
|
50
|
+
expect(getCreditAmountForPurchase(unknownAmount)).toBe(unknownAmount);
|
|
51
|
+
});
|
|
52
|
+
it("bonus tier percentages produce correct creditCents (not raw credit units)", () => {
|
|
53
|
+
// $25 + 2% bonus = $25.50 = 2550 cents (NOT 25_500_000_000 nanodollars)
|
|
54
|
+
const tier25 = CREDIT_PRICE_POINTS.find((p) => p.amountCents === 2500);
|
|
55
|
+
expect(tier25).not.toBeNull();
|
|
56
|
+
expect(tier25?.creditCents).toBe(2550);
|
|
57
|
+
// $100 + 10% bonus = $110 = 11000 cents (NOT 110_000_000_000 nanodollars)
|
|
58
|
+
const tier100 = CREDIT_PRICE_POINTS.find((p) => p.amountCents === 10000);
|
|
59
|
+
expect(tier100).not.toBeNull();
|
|
60
|
+
expect(tier100?.creditCents).toBe(11000);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { ITenantCustomerRepository, TenantCustomerRepository } from "./tenant-store.js";
|
|
3
|
+
import type { CreditCheckoutOpts, VpsCheckoutOpts } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Create a Stripe Checkout session for a one-time credit purchase.
|
|
6
|
+
*
|
|
7
|
+
* Uses mode: "payment" (not "subscription") — credits are purchased
|
|
8
|
+
* as one-time payments and credited to the tenant's ledger via webhook.
|
|
9
|
+
*
|
|
10
|
+
* If the tenant already has a Stripe customer, it reuses that customer.
|
|
11
|
+
* Otherwise, Stripe creates a new customer during checkout.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createCreditCheckoutSession(stripe: Stripe, tenantRepo: TenantCustomerRepository, opts: CreditCheckoutOpts): Promise<Stripe.Checkout.Session>;
|
|
14
|
+
/**
|
|
15
|
+
* Create a Stripe Checkout session for a VPS subscription.
|
|
16
|
+
*
|
|
17
|
+
* Uses mode: "subscription" — VPS is a $15/month recurring subscription.
|
|
18
|
+
* The webhook handler processes customer.subscription.created to activate VPS status.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createVpsCheckoutSession(stripe: Stripe, tenantRepo: ITenantCustomerRepository, opts: VpsCheckoutOpts): Promise<Stripe.Checkout.Session>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a Stripe Checkout session for a one-time credit purchase.
|
|
3
|
+
*
|
|
4
|
+
* Uses mode: "payment" (not "subscription") — credits are purchased
|
|
5
|
+
* as one-time payments and credited to the tenant's ledger via webhook.
|
|
6
|
+
*
|
|
7
|
+
* If the tenant already has a Stripe customer, it reuses that customer.
|
|
8
|
+
* Otherwise, Stripe creates a new customer during checkout.
|
|
9
|
+
*/
|
|
10
|
+
export async function createCreditCheckoutSession(stripe, tenantRepo, opts) {
|
|
11
|
+
const existing = await tenantRepo.getByTenant(opts.tenant);
|
|
12
|
+
const params = {
|
|
13
|
+
mode: "payment",
|
|
14
|
+
line_items: [
|
|
15
|
+
{
|
|
16
|
+
price: opts.priceId,
|
|
17
|
+
quantity: 1,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
success_url: opts.successUrl,
|
|
21
|
+
cancel_url: opts.cancelUrl,
|
|
22
|
+
client_reference_id: opts.tenant,
|
|
23
|
+
metadata: {
|
|
24
|
+
wopr_tenant: opts.tenant,
|
|
25
|
+
wopr_purchase_type: "credits",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
// Reuse existing Stripe customer if we have one.
|
|
29
|
+
if (existing) {
|
|
30
|
+
params.customer = existing.processor_customer_id;
|
|
31
|
+
}
|
|
32
|
+
return stripe.checkout.sessions.create(params);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a Stripe Checkout session for a VPS subscription.
|
|
36
|
+
*
|
|
37
|
+
* Uses mode: "subscription" — VPS is a $15/month recurring subscription.
|
|
38
|
+
* The webhook handler processes customer.subscription.created to activate VPS status.
|
|
39
|
+
*/
|
|
40
|
+
export async function createVpsCheckoutSession(stripe, tenantRepo, opts) {
|
|
41
|
+
const existing = await tenantRepo.getByTenant(opts.tenant);
|
|
42
|
+
const params = {
|
|
43
|
+
mode: "subscription",
|
|
44
|
+
line_items: [
|
|
45
|
+
{
|
|
46
|
+
price: opts.vpsPriceId,
|
|
47
|
+
quantity: 1,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
success_url: opts.successUrl,
|
|
51
|
+
cancel_url: opts.cancelUrl,
|
|
52
|
+
client_reference_id: opts.tenant,
|
|
53
|
+
metadata: {
|
|
54
|
+
wopr_tenant: opts.tenant,
|
|
55
|
+
wopr_bot_id: opts.botId,
|
|
56
|
+
wopr_purchase_type: "vps",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
if (existing) {
|
|
60
|
+
params.customer = existing.processor_customer_id;
|
|
61
|
+
}
|
|
62
|
+
return stripe.checkout.sessions.create(params);
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createCreditCheckoutSession, createVpsCheckoutSession } from "./checkout.js";
|
|
3
|
+
describe("createCreditCheckoutSession", () => {
|
|
4
|
+
function mockStripe(sessionCreateResult = { id: "cs_test", url: "https://checkout.stripe.com/cs_test" }) {
|
|
5
|
+
return {
|
|
6
|
+
checkout: {
|
|
7
|
+
sessions: {
|
|
8
|
+
create: vi.fn().mockResolvedValue(sessionCreateResult),
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function mockTenantStore(existingMapping = null) {
|
|
14
|
+
return {
|
|
15
|
+
getByTenant: vi.fn().mockReturnValue(existingMapping),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
it("creates a payment-mode checkout session", async () => {
|
|
19
|
+
const stripe = mockStripe();
|
|
20
|
+
const store = mockTenantStore();
|
|
21
|
+
const session = await createCreditCheckoutSession(stripe, store, {
|
|
22
|
+
tenant: "t-1",
|
|
23
|
+
priceId: "price_abc",
|
|
24
|
+
successUrl: "https://example.com/success",
|
|
25
|
+
cancelUrl: "https://example.com/cancel",
|
|
26
|
+
});
|
|
27
|
+
expect(session.id).toBe("cs_test");
|
|
28
|
+
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
29
|
+
mode: "payment",
|
|
30
|
+
line_items: [{ price: "price_abc", quantity: 1 }],
|
|
31
|
+
success_url: "https://example.com/success",
|
|
32
|
+
cancel_url: "https://example.com/cancel",
|
|
33
|
+
client_reference_id: "t-1",
|
|
34
|
+
metadata: expect.objectContaining({ wopr_tenant: "t-1" }),
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
it("reuses existing Stripe customer when available", async () => {
|
|
38
|
+
const stripe = mockStripe();
|
|
39
|
+
const store = mockTenantStore({ processor_customer_id: "cus_existing" });
|
|
40
|
+
await createCreditCheckoutSession(stripe, store, {
|
|
41
|
+
tenant: "t-1",
|
|
42
|
+
priceId: "price_abc",
|
|
43
|
+
successUrl: "https://example.com/s",
|
|
44
|
+
cancelUrl: "https://example.com/c",
|
|
45
|
+
});
|
|
46
|
+
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(expect.objectContaining({ customer: "cus_existing" }));
|
|
47
|
+
});
|
|
48
|
+
it("does not set customer when no existing mapping", async () => {
|
|
49
|
+
const stripe = mockStripe();
|
|
50
|
+
const store = mockTenantStore(null);
|
|
51
|
+
await createCreditCheckoutSession(stripe, store, {
|
|
52
|
+
tenant: "t-new",
|
|
53
|
+
priceId: "price_abc",
|
|
54
|
+
successUrl: "https://example.com/s",
|
|
55
|
+
cancelUrl: "https://example.com/c",
|
|
56
|
+
});
|
|
57
|
+
const callArgs = stripe.checkout.sessions.create.mock.calls[0][0];
|
|
58
|
+
expect(callArgs.customer).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
it("propagates Stripe API errors", async () => {
|
|
61
|
+
const stripe = mockStripe();
|
|
62
|
+
stripe.checkout.sessions.create.mockRejectedValue(new Error("Stripe error"));
|
|
63
|
+
const store = mockTenantStore();
|
|
64
|
+
await expect(createCreditCheckoutSession(stripe, store, {
|
|
65
|
+
tenant: "t-1",
|
|
66
|
+
priceId: "price_abc",
|
|
67
|
+
successUrl: "https://example.com/s",
|
|
68
|
+
cancelUrl: "https://example.com/c",
|
|
69
|
+
})).rejects.toThrow("Stripe error");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("createVpsCheckoutSession", () => {
|
|
73
|
+
function mockStripe(sessionCreateResult = { id: "cs_vps_test", url: "https://checkout.stripe.com/cs_vps_test" }) {
|
|
74
|
+
return {
|
|
75
|
+
checkout: {
|
|
76
|
+
sessions: {
|
|
77
|
+
create: vi.fn().mockResolvedValue(sessionCreateResult),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function mockTenantStore(existingMapping = null) {
|
|
83
|
+
return {
|
|
84
|
+
getByTenant: vi.fn().mockReturnValue(existingMapping),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
it("creates a subscription-mode checkout session", async () => {
|
|
88
|
+
const stripe = mockStripe();
|
|
89
|
+
const store = mockTenantStore();
|
|
90
|
+
const session = await createVpsCheckoutSession(stripe, store, {
|
|
91
|
+
tenant: "t-1",
|
|
92
|
+
botId: "bot-abc",
|
|
93
|
+
vpsPriceId: "price_vps_15",
|
|
94
|
+
successUrl: "https://example.com/success",
|
|
95
|
+
cancelUrl: "https://example.com/cancel",
|
|
96
|
+
});
|
|
97
|
+
expect(session.id).toBe("cs_vps_test");
|
|
98
|
+
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
99
|
+
mode: "subscription",
|
|
100
|
+
line_items: [{ price: "price_vps_15", quantity: 1 }],
|
|
101
|
+
success_url: "https://example.com/success",
|
|
102
|
+
cancel_url: "https://example.com/cancel",
|
|
103
|
+
client_reference_id: "t-1",
|
|
104
|
+
metadata: expect.objectContaining({
|
|
105
|
+
wopr_tenant: "t-1",
|
|
106
|
+
wopr_bot_id: "bot-abc",
|
|
107
|
+
wopr_purchase_type: "vps",
|
|
108
|
+
}),
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
it("reuses existing Stripe customer when available", async () => {
|
|
112
|
+
const stripe = mockStripe();
|
|
113
|
+
const store = mockTenantStore({ processor_customer_id: "cus_existing" });
|
|
114
|
+
await createVpsCheckoutSession(stripe, store, {
|
|
115
|
+
tenant: "t-1",
|
|
116
|
+
botId: "bot-abc",
|
|
117
|
+
vpsPriceId: "price_vps_15",
|
|
118
|
+
successUrl: "https://example.com/s",
|
|
119
|
+
cancelUrl: "https://example.com/c",
|
|
120
|
+
});
|
|
121
|
+
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(expect.objectContaining({ customer: "cus_existing" }));
|
|
122
|
+
});
|
|
123
|
+
it("does not set customer when no existing mapping", async () => {
|
|
124
|
+
const stripe = mockStripe();
|
|
125
|
+
const store = mockTenantStore(null);
|
|
126
|
+
await createVpsCheckoutSession(stripe, store, {
|
|
127
|
+
tenant: "t-new",
|
|
128
|
+
botId: "bot-xyz",
|
|
129
|
+
vpsPriceId: "price_vps_15",
|
|
130
|
+
successUrl: "https://example.com/s",
|
|
131
|
+
cancelUrl: "https://example.com/c",
|
|
132
|
+
});
|
|
133
|
+
const callArgs = stripe.checkout.sessions.create.mock.calls[0][0];
|
|
134
|
+
expect(callArgs.customer).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
it("propagates Stripe API errors", async () => {
|
|
137
|
+
const stripe = mockStripe();
|
|
138
|
+
stripe.checkout.sessions.create.mockRejectedValue(new Error("VPS Stripe error"));
|
|
139
|
+
const store = mockTenantStore();
|
|
140
|
+
await expect(createVpsCheckoutSession(stripe, store, {
|
|
141
|
+
tenant: "t-1",
|
|
142
|
+
botId: "bot-abc",
|
|
143
|
+
vpsPriceId: "price_vps_15",
|
|
144
|
+
successUrl: "https://example.com/s",
|
|
145
|
+
cancelUrl: "https://example.com/c",
|
|
146
|
+
})).rejects.toThrow("VPS Stripe error");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
import type { StripeBillingConfig } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a configured Stripe client.
|
|
5
|
+
*
|
|
6
|
+
* All Stripe config comes from env vars — no billing logic in WOPR,
|
|
7
|
+
* just a thin wrapper around the Stripe SDK.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createStripeClient(config: StripeBillingConfig): Stripe;
|
|
10
|
+
/**
|
|
11
|
+
* Load Stripe billing config from environment variables.
|
|
12
|
+
* Returns null if required vars are missing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadStripeConfig(): StripeBillingConfig | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
/**
|
|
3
|
+
* The Stripe API version to use for all requests.
|
|
4
|
+
* Pinning this ensures stability across Stripe API releases.
|
|
5
|
+
*/
|
|
6
|
+
const STRIPE_API_VERSION = "2024-12-18.acacia";
|
|
7
|
+
/**
|
|
8
|
+
* Create a configured Stripe client.
|
|
9
|
+
*
|
|
10
|
+
* All Stripe config comes from env vars — no billing logic in WOPR,
|
|
11
|
+
* just a thin wrapper around the Stripe SDK.
|
|
12
|
+
*/
|
|
13
|
+
export function createStripeClient(config) {
|
|
14
|
+
return new Stripe(config.secretKey, {
|
|
15
|
+
// @ts-expect-error stripe-version-2024-12-18.acacia
|
|
16
|
+
apiVersion: STRIPE_API_VERSION,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load Stripe billing config from environment variables.
|
|
21
|
+
* Returns null if required vars are missing.
|
|
22
|
+
*/
|
|
23
|
+
export function loadStripeConfig() {
|
|
24
|
+
const secretKey = process.env.STRIPE_SECRET_KEY;
|
|
25
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
26
|
+
if (!secretKey || !webhookSecret) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
secretKey,
|
|
31
|
+
webhookSecret,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createStripeClient, loadStripeConfig } from "./client.js";
|
|
3
|
+
describe("loadStripeConfig", () => {
|
|
4
|
+
let origSecretKey;
|
|
5
|
+
let origWebhookSecret;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
origSecretKey = process.env.STRIPE_SECRET_KEY;
|
|
8
|
+
origWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (origSecretKey === undefined) {
|
|
12
|
+
delete process.env.STRIPE_SECRET_KEY;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
process.env.STRIPE_SECRET_KEY = origSecretKey;
|
|
16
|
+
}
|
|
17
|
+
if (origWebhookSecret === undefined) {
|
|
18
|
+
delete process.env.STRIPE_WEBHOOK_SECRET;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
process.env.STRIPE_WEBHOOK_SECRET = origWebhookSecret;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
it("returns null when both env vars are missing", () => {
|
|
25
|
+
delete process.env.STRIPE_SECRET_KEY;
|
|
26
|
+
delete process.env.STRIPE_WEBHOOK_SECRET;
|
|
27
|
+
expect(loadStripeConfig()).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it("returns null when only STRIPE_SECRET_KEY is set", () => {
|
|
30
|
+
process.env.STRIPE_SECRET_KEY = "sk_test_abc";
|
|
31
|
+
delete process.env.STRIPE_WEBHOOK_SECRET;
|
|
32
|
+
expect(loadStripeConfig()).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it("returns null when only STRIPE_WEBHOOK_SECRET is set", () => {
|
|
35
|
+
delete process.env.STRIPE_SECRET_KEY;
|
|
36
|
+
process.env.STRIPE_WEBHOOK_SECRET = "whsec_abc";
|
|
37
|
+
expect(loadStripeConfig()).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it("returns config when both env vars are present", () => {
|
|
40
|
+
process.env.STRIPE_SECRET_KEY = "sk_test_abc123";
|
|
41
|
+
process.env.STRIPE_WEBHOOK_SECRET = "whsec_def456";
|
|
42
|
+
const config = loadStripeConfig();
|
|
43
|
+
expect(config).not.toBeNull();
|
|
44
|
+
expect(config?.secretKey).toBe("sk_test_abc123");
|
|
45
|
+
expect(config?.webhookSecret).toBe("whsec_def456");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("createStripeClient", () => {
|
|
49
|
+
it("creates a Stripe client with pinned API version", () => {
|
|
50
|
+
const client = createStripeClient({ secretKey: "sk_test_abc", webhookSecret: "whsec_abc" });
|
|
51
|
+
// Stripe client stores the API version internally
|
|
52
|
+
// We verify it was created successfully
|
|
53
|
+
expect(client).toEqual(expect.any(Object));
|
|
54
|
+
expect(client.checkout).toEqual(expect.any(Object));
|
|
55
|
+
expect(client.paymentIntents).toEqual(expect.any(Object));
|
|
56
|
+
expect(client.setupIntents).toEqual(expect.any(Object));
|
|
57
|
+
});
|
|
58
|
+
});
|