@strav/payment 1.0.0-alpha.24
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/package.json +34 -0
- package/src/drivers/index.ts +6 -0
- package/src/drivers/mock_driver.ts +534 -0
- package/src/drivers/omise/index.ts +56 -0
- package/src/drivers/omise/omise_config.ts +19 -0
- package/src/drivers/omise/omise_driver.ts +576 -0
- package/src/drivers/omise/omise_mappers.ts +180 -0
- package/src/drivers/omise/omise_method_spec.ts +88 -0
- package/src/drivers/omise/omise_next_action_mapper.ts +89 -0
- package/src/drivers/omise/omise_price_spec.ts +85 -0
- package/src/drivers/omise/omise_provider.ts +33 -0
- package/src/drivers/omise/omise_schedule_mapper.ts +156 -0
- package/src/drivers/omise/omise_webhook.ts +162 -0
- package/src/drivers/payment_method_helpers.ts +35 -0
- package/src/drivers/stripe/index.ts +40 -0
- package/src/drivers/stripe/mappers/stripe_mappers.ts +312 -0
- package/src/drivers/stripe/mappers/stripe_method_spec.ts +77 -0
- package/src/drivers/stripe/mappers/stripe_next_action_mapper.ts +163 -0
- package/src/drivers/stripe/stripe_config.ts +18 -0
- package/src/drivers/stripe/stripe_driver.ts +650 -0
- package/src/drivers/stripe/stripe_provider.ts +38 -0
- package/src/drivers/stripe/webhook/stripe_normalize.ts +139 -0
- package/src/drivers/unsupported.ts +20 -0
- package/src/dto/index.ts +72 -0
- package/src/dto/payment_charge.ts +158 -0
- package/src/dto/payment_checkout.ts +46 -0
- package/src/dto/payment_customer.ts +52 -0
- package/src/dto/payment_event.ts +83 -0
- package/src/dto/payment_invoice.ts +39 -0
- package/src/dto/payment_link.ts +81 -0
- package/src/dto/payment_method.ts +43 -0
- package/src/dto/payment_price.ts +47 -0
- package/src/dto/payment_product.ts +40 -0
- package/src/dto/payment_subscription.ts +71 -0
- package/src/index.ts +78 -0
- package/src/ledger/apply_payment_ledger_migration.ts +106 -0
- package/src/ledger/index.ts +13 -0
- package/src/ledger/payment_ledger.ts +260 -0
- package/src/ledger/payment_ledger_models.ts +66 -0
- package/src/ledger/schemas/payment_customer_schema.ts +34 -0
- package/src/ledger/schemas/payment_invoice_schema.ts +39 -0
- package/src/ledger/schemas/payment_subscription_schema.ts +34 -0
- package/src/payment_capabilities.ts +91 -0
- package/src/payment_driver.ts +167 -0
- package/src/payment_error.ts +159 -0
- package/src/payment_manager.ts +174 -0
- package/src/payment_provider.ts +93 -0
- package/src/tenant_metadata.ts +60 -0
- package/src/types.ts +49 -0
- package/src/webhook/index.ts +8 -0
- package/src/webhook/payment_webhook.ts +190 -0
- package/src/webhook/payment_webhook_event.ts +22 -0
- package/src/webhook/payment_webhook_event_repository.ts +65 -0
- package/src/webhook/payment_webhook_event_schema.ts +40 -0
- package/src/webhook/payment_webhook_registry.ts +65 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `paymentWebhookEventSchema` — system-wide dedup ledger for
|
|
3
|
+
* incoming webhooks from every configured provider.
|
|
4
|
+
*
|
|
5
|
+
* On every delivery (after signature verification), the framework
|
|
6
|
+
* does:
|
|
7
|
+
*
|
|
8
|
+
* INSERT INTO payment_webhook_event (...) ON CONFLICT DO NOTHING
|
|
9
|
+
*
|
|
10
|
+
* The first delivery wins the INSERT and fires user handlers;
|
|
11
|
+
* subsequent deliveries (provider retries, concurrent webhook
|
|
12
|
+
* workers) see the conflict and return 200 without re-firing.
|
|
13
|
+
*
|
|
14
|
+
* Why NOT tenanted: webhooks arrive without tenant context. The
|
|
15
|
+
* endpoint can't know which tenant a payload belongs to until
|
|
16
|
+
* after signature verification + payload inspection — too late
|
|
17
|
+
* for the framework-level dedup INSERT.
|
|
18
|
+
*
|
|
19
|
+
* Why a composite unique key `(provider, provider_event_id)`:
|
|
20
|
+
* different providers may emit colliding event id formats; the
|
|
21
|
+
* pair is the actual uniqueness contract.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { Archetype, defineSchema } from '@strav/database'
|
|
25
|
+
|
|
26
|
+
export const paymentWebhookEventSchema = defineSchema(
|
|
27
|
+
'payment_webhook_event',
|
|
28
|
+
Archetype.Event,
|
|
29
|
+
(t) => {
|
|
30
|
+
t.id()
|
|
31
|
+
t.string('provider').max(64).notNull()
|
|
32
|
+
t.string('provider_event_id').max(255).notNull()
|
|
33
|
+
t.string('event_type').max(128).notNull()
|
|
34
|
+
t.timestamp('received_at').notNull()
|
|
35
|
+
t.timestamp('processed_at').nullable()
|
|
36
|
+
// Composite (provider, provider_event_id) unique constraint
|
|
37
|
+
// is added by `applyPaymentLedgerMigration` — the schema
|
|
38
|
+
// builder only exposes per-column `.unique()`.
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler registry for normalized payment webhook events.
|
|
3
|
+
*
|
|
4
|
+
* Apps register handlers at boot:
|
|
5
|
+
*
|
|
6
|
+
* payment.onWebhookEvent('subscription.created', (ctx) => { ... })
|
|
7
|
+
* payment.onWebhookEvent('charge.succeeded', { provider: 'stripe' }, (ctx) => { ... })
|
|
8
|
+
*
|
|
9
|
+
* Handlers fire in registration order. A thrown handler aborts
|
|
10
|
+
* the rest and surfaces a 500 (the provider retries). Multiple
|
|
11
|
+
* handlers per `(eventType, provider?)` are fine.
|
|
12
|
+
*
|
|
13
|
+
* Filter semantics: when `filter.provider` is set, the handler
|
|
14
|
+
* only fires for that provider; when omitted, the handler fires
|
|
15
|
+
* for any provider that emits the type.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
PaymentEventType,
|
|
20
|
+
WebhookHandler,
|
|
21
|
+
WebhookHandlerFilter,
|
|
22
|
+
} from '../dto/payment_event.ts'
|
|
23
|
+
|
|
24
|
+
interface RegisteredHandler {
|
|
25
|
+
filter: WebhookHandlerFilter
|
|
26
|
+
handler: WebhookHandler
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PaymentWebhookRegistry {
|
|
30
|
+
private readonly handlers = new Map<PaymentEventType, RegisteredHandler[]>()
|
|
31
|
+
|
|
32
|
+
on(eventType: PaymentEventType, handler: WebhookHandler): void
|
|
33
|
+
on(
|
|
34
|
+
eventType: PaymentEventType,
|
|
35
|
+
filter: WebhookHandlerFilter,
|
|
36
|
+
handler: WebhookHandler,
|
|
37
|
+
): void
|
|
38
|
+
on(
|
|
39
|
+
eventType: PaymentEventType,
|
|
40
|
+
filterOrHandler: WebhookHandlerFilter | WebhookHandler,
|
|
41
|
+
maybeHandler?: WebhookHandler,
|
|
42
|
+
): void {
|
|
43
|
+
const { filter, handler } =
|
|
44
|
+
typeof filterOrHandler === 'function'
|
|
45
|
+
? { filter: {}, handler: filterOrHandler }
|
|
46
|
+
: { filter: filterOrHandler, handler: maybeHandler! }
|
|
47
|
+
const existing = this.handlers.get(eventType) ?? []
|
|
48
|
+
existing.push({ filter, handler })
|
|
49
|
+
this.handlers.set(eventType, existing)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Remove every registered handler. Tests use this to keep cases isolated. */
|
|
53
|
+
clear(): void {
|
|
54
|
+
this.handlers.clear()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resolve the handlers that match a given `(type, provider)` pair. */
|
|
58
|
+
resolve(eventType: PaymentEventType, provider: string): readonly WebhookHandler[] {
|
|
59
|
+
const matches = this.handlers.get(eventType)
|
|
60
|
+
if (!matches) return []
|
|
61
|
+
return matches
|
|
62
|
+
.filter((m) => !m.filter.provider || m.filter.provider === provider)
|
|
63
|
+
.map((m) => m.handler)
|
|
64
|
+
}
|
|
65
|
+
}
|