@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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map `Stripe.PaymentIntent.NextAction` onto the framework's
|
|
3
|
+
* `PaymentNextAction` union.
|
|
4
|
+
*
|
|
5
|
+
* Stripe's discriminator is `next_action.type`; each variant
|
|
6
|
+
* carries its own field cluster (`promptpay_display_qr_code`,
|
|
7
|
+
* `wechat_pay_display_qr_code`, `redirect_to_url`,
|
|
8
|
+
* `konbini_display_details`, …). We collapse them onto the four
|
|
9
|
+
* framework kinds: `display_qr`, `redirect`, `authorize`,
|
|
10
|
+
* `voucher` (plus `wait` for variants without user-facing
|
|
11
|
+
* detail).
|
|
12
|
+
*
|
|
13
|
+
* Why this lives in `mappers/` (not the driver): the same
|
|
14
|
+
* mapping is useful from app code that pokes the raw intent
|
|
15
|
+
* (`driver.client.paymentIntents.retrieve(id)`) and wants the
|
|
16
|
+
* framework-shaped DTO without re-running `charges.create`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type Stripe from 'stripe'
|
|
20
|
+
import type { PaymentNextAction } from '../../../dto/index.ts'
|
|
21
|
+
|
|
22
|
+
function maybeDate(unix: number | null | undefined): Date | undefined {
|
|
23
|
+
if (unix === null || unix === undefined) return undefined
|
|
24
|
+
return new Date(unix * 1000)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns `null` when there's no actionable step (intent is
|
|
29
|
+
* already settled / failed / cancelled, or carries a variant we
|
|
30
|
+
* don't expose). Apps fall back to `intent.next_action` on `raw`
|
|
31
|
+
* for variants we haven't surfaced.
|
|
32
|
+
*/
|
|
33
|
+
export function stripeNextAction(
|
|
34
|
+
na: Stripe.PaymentIntent.NextAction | null | undefined,
|
|
35
|
+
): PaymentNextAction | null {
|
|
36
|
+
if (!na) return null
|
|
37
|
+
switch (na.type) {
|
|
38
|
+
// ─── QR-based ────────────────────────────────────────────────────────
|
|
39
|
+
case 'promptpay_display_qr_code': {
|
|
40
|
+
const d = na.promptpay_display_qr_code
|
|
41
|
+
if (!d) return { kind: 'wait' }
|
|
42
|
+
const action: PaymentNextAction = {
|
|
43
|
+
kind: 'display_qr',
|
|
44
|
+
qrData: d.data ?? '',
|
|
45
|
+
}
|
|
46
|
+
if (d.image_url_png) action.qrImageUrl = d.image_url_png
|
|
47
|
+
return action
|
|
48
|
+
}
|
|
49
|
+
case 'paynow_display_qr_code': {
|
|
50
|
+
const d = na.paynow_display_qr_code
|
|
51
|
+
if (!d) return { kind: 'wait' }
|
|
52
|
+
const action: PaymentNextAction = {
|
|
53
|
+
kind: 'display_qr',
|
|
54
|
+
qrData: d.data ?? '',
|
|
55
|
+
}
|
|
56
|
+
if (d.image_url_png) action.qrImageUrl = d.image_url_png
|
|
57
|
+
return action
|
|
58
|
+
}
|
|
59
|
+
case 'wechat_pay_display_qr_code': {
|
|
60
|
+
const d = na.wechat_pay_display_qr_code
|
|
61
|
+
if (!d) return { kind: 'wait' }
|
|
62
|
+
const action: PaymentNextAction = {
|
|
63
|
+
kind: 'display_qr',
|
|
64
|
+
qrData: d.data ?? '',
|
|
65
|
+
}
|
|
66
|
+
if (d.image_url_png) action.qrImageUrl = d.image_url_png
|
|
67
|
+
return action
|
|
68
|
+
}
|
|
69
|
+
case 'cashapp_handle_redirect_or_display_qr_code':
|
|
70
|
+
case 'swish_handle_redirect_or_display_qr_code': {
|
|
71
|
+
// Hybrid variant — Stripe gives both a redirect URL and a
|
|
72
|
+
// QR; we surface the QR (more universal for desktop checkout).
|
|
73
|
+
const d = (na as unknown as {
|
|
74
|
+
cashapp_handle_redirect_or_display_qr_code?: { qr_code?: { data?: string; image_url_png?: string }; hosted_instructions_url?: string }
|
|
75
|
+
swish_handle_redirect_or_display_qr_code?: { qr_code?: { data?: string; image_url_png?: string }; hosted_instructions_url?: string }
|
|
76
|
+
})[na.type]
|
|
77
|
+
const qr = d?.qr_code
|
|
78
|
+
if (qr?.data) {
|
|
79
|
+
const action: PaymentNextAction = { kind: 'display_qr', qrData: qr.data }
|
|
80
|
+
if (qr.image_url_png) action.qrImageUrl = qr.image_url_png
|
|
81
|
+
return action
|
|
82
|
+
}
|
|
83
|
+
if (d?.hosted_instructions_url) {
|
|
84
|
+
return { kind: 'redirect', url: d.hosted_instructions_url }
|
|
85
|
+
}
|
|
86
|
+
return { kind: 'wait' }
|
|
87
|
+
}
|
|
88
|
+
// ─── Redirect-based ─────────────────────────────────────────────────
|
|
89
|
+
case 'alipay_handle_redirect': {
|
|
90
|
+
const d = na.alipay_handle_redirect
|
|
91
|
+
if (!d?.url) return { kind: 'wait' }
|
|
92
|
+
return { kind: 'redirect', url: d.url }
|
|
93
|
+
}
|
|
94
|
+
case 'wechat_pay_redirect_to_android_app':
|
|
95
|
+
case 'wechat_pay_redirect_to_ios_app': {
|
|
96
|
+
const url = (na as unknown as {
|
|
97
|
+
wechat_pay_redirect_to_android_app?: { data?: string }
|
|
98
|
+
wechat_pay_redirect_to_ios_app?: { native_url?: string }
|
|
99
|
+
})[na.type]
|
|
100
|
+
const target =
|
|
101
|
+
(url as { native_url?: string })?.native_url ??
|
|
102
|
+
(url as { data?: string })?.data
|
|
103
|
+
if (!target) return { kind: 'wait' }
|
|
104
|
+
return { kind: 'redirect', url: target }
|
|
105
|
+
}
|
|
106
|
+
case 'redirect_to_url': {
|
|
107
|
+
const d = na.redirect_to_url
|
|
108
|
+
if (!d?.url) return { kind: 'wait' }
|
|
109
|
+
// 3DS card challenges + most non-card wallet redirects flow
|
|
110
|
+
// through this variant. Stripe doesn't tag which one — we
|
|
111
|
+
// pick `redirect`; apps that need to distinguish read
|
|
112
|
+
// `raw.next_action` (the intent's payment_method type tells
|
|
113
|
+
// the truth).
|
|
114
|
+
return { kind: 'redirect', url: d.url }
|
|
115
|
+
}
|
|
116
|
+
case 'use_stripe_sdk': {
|
|
117
|
+
// Card 3DS challenge — Stripe.js handles the UI, but apps
|
|
118
|
+
// calling server-side need to know an authorize step is
|
|
119
|
+
// pending. Stripe doesn't surface a server-side URL here;
|
|
120
|
+
// apps drive Stripe.js from the publishable key.
|
|
121
|
+
return { kind: 'authorize', url: '' }
|
|
122
|
+
}
|
|
123
|
+
// ─── Voucher / convenience-store ────────────────────────────────────
|
|
124
|
+
case 'konbini_display_details': {
|
|
125
|
+
const d = na.konbini_display_details
|
|
126
|
+
const ref =
|
|
127
|
+
d?.stores?.familymart?.confirmation_number ??
|
|
128
|
+
d?.stores?.lawson?.confirmation_number ??
|
|
129
|
+
d?.stores?.ministop?.confirmation_number ??
|
|
130
|
+
d?.stores?.seicomart?.confirmation_number ??
|
|
131
|
+
''
|
|
132
|
+
const action: PaymentNextAction = { kind: 'voucher', reference: ref }
|
|
133
|
+
const expires = maybeDate(d?.expires_at)
|
|
134
|
+
if (expires) action.expiresAt = expires
|
|
135
|
+
if (d?.hosted_voucher_url) {
|
|
136
|
+
// Stripe doesn't expose a barcode image directly; the
|
|
137
|
+
// hosted voucher URL is the canonical display.
|
|
138
|
+
action.barcodeImageUrl = d.hosted_voucher_url
|
|
139
|
+
}
|
|
140
|
+
return action
|
|
141
|
+
}
|
|
142
|
+
case 'boleto_display_details':
|
|
143
|
+
case 'oxxo_display_details':
|
|
144
|
+
case 'display_oxxo_details':
|
|
145
|
+
case 'multibanco_display_details': {
|
|
146
|
+
const d = (na as unknown as Record<string, { number?: string; hosted_voucher_url?: string; expires_at?: number }>)[na.type]
|
|
147
|
+
const action: PaymentNextAction = {
|
|
148
|
+
kind: 'voucher',
|
|
149
|
+
reference: d?.number ?? '',
|
|
150
|
+
}
|
|
151
|
+
const expires = maybeDate(d?.expires_at)
|
|
152
|
+
if (expires) action.expiresAt = expires
|
|
153
|
+
if (d?.hosted_voucher_url) action.barcodeImageUrl = d.hosted_voucher_url
|
|
154
|
+
return action
|
|
155
|
+
}
|
|
156
|
+
// ─── No user-facing action ──────────────────────────────────────────
|
|
157
|
+
case 'verify_with_microdeposits':
|
|
158
|
+
case 'card_await_notification':
|
|
159
|
+
return { kind: 'wait' }
|
|
160
|
+
default:
|
|
161
|
+
return { kind: 'wait' }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stripe-specific provider config. Apps put one of these inside
|
|
3
|
+
* `config.payment.providers[name]` with `driver: 'stripe'`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ProviderConfig } from '../../types.ts'
|
|
7
|
+
|
|
8
|
+
export interface StripeProviderConfig extends ProviderConfig {
|
|
9
|
+
driver: 'stripe'
|
|
10
|
+
/** `sk_test_...` / `sk_live_...`. Required. */
|
|
11
|
+
secret: string
|
|
12
|
+
/** `whsec_...` from the Stripe Dashboard. Required for webhook routes. */
|
|
13
|
+
webhookSecret?: string
|
|
14
|
+
/** Pin the SDK to a specific API version. Defaults to SDK-bundled. */
|
|
15
|
+
apiVersion?: string
|
|
16
|
+
/** Optional: pass a pre-built `Stripe` instance (tests). */
|
|
17
|
+
client?: unknown
|
|
18
|
+
}
|