@umituz/web-payment 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +349 -2
- package/dist/billing-adapter.interface-CDkeeowZ.d.mts +94 -0
- package/dist/billing-adapter.interface-m7yE50I0.d.ts +94 -0
- package/dist/billing-cycle.enum--hJRYlgP.d.mts +10 -0
- package/dist/billing-cycle.enum--hJRYlgP.d.ts +10 -0
- package/dist/billing-env-D4YjvA3M.d.mts +8 -0
- package/dist/billing-env-D4YjvA3M.d.ts +8 -0
- package/dist/checkout-metadata-D7Wp_DAD.d.mts +27 -0
- package/dist/checkout-metadata-D7Wp_DAD.d.ts +27 -0
- package/dist/chunk-2KV7JFYF.mjs +61 -0
- package/dist/chunk-2NH5DOLT.mjs +101 -0
- package/dist/chunk-3AXPZOCV.mjs +106 -0
- package/dist/chunk-4N3BASZ6.mjs +51 -0
- package/dist/chunk-NHKK3ENU.mjs +1128 -0
- package/dist/chunk-PIWNUAKW.mjs +30 -0
- package/dist/chunk-QFB7AOSQ.mjs +33 -0
- package/dist/chunk-RQTMBR74.mjs +30 -0
- package/dist/chunk-U45FS4FF.mjs +110 -0
- package/dist/chunk-X6R76FZT.mjs +0 -0
- package/dist/chunk-XQ2HJTTA.mjs +252 -0
- package/dist/core.d.mts +17 -0
- package/dist/core.d.ts +17 -0
- package/dist/core.js +425 -0
- package/dist/core.mjs +66 -0
- package/dist/http.d.mts +12 -0
- package/dist/http.d.ts +12 -0
- package/dist/http.js +115 -0
- package/dist/http.mjs +88 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1929 -0
- package/dist/index.mjs +153 -0
- package/dist/memory.d.mts +14 -0
- package/dist/memory.d.ts +14 -0
- package/dist/memory.js +130 -0
- package/dist/memory.mjs +6 -0
- package/dist/portal.d.mts +445 -0
- package/dist/portal.d.ts +445 -0
- package/dist/portal.js +2927 -0
- package/dist/portal.mjs +2737 -0
- package/dist/product.mapper-CnY1ISOp.d.ts +97 -0
- package/dist/product.mapper-noVl-sel.d.mts +97 -0
- package/dist/products.d.mts +64 -0
- package/dist/products.d.ts +64 -0
- package/dist/products.js +370 -0
- package/dist/products.mjs +21 -0
- package/dist/react.d.mts +182 -0
- package/dist/react.d.ts +182 -0
- package/dist/react.js +1874 -0
- package/dist/react.mjs +105 -0
- package/dist/server.d.mts +177 -0
- package/dist/server.d.ts +177 -0
- package/dist/server.js +622 -0
- package/dist/server.mjs +417 -0
- package/dist/visual-tokens-BCLVR85G.d.mts +94 -0
- package/dist/visual-tokens-BCLVR85G.d.ts +94 -0
- package/package.json +122 -4
- package/src/core.ts +81 -0
- package/src/domain/entities/cancellation.entity.ts +28 -0
- package/src/domain/entities/checkout.entity.ts +21 -0
- package/src/domain/entities/index.ts +10 -0
- package/src/domain/entities/order.entity.ts +15 -0
- package/src/domain/entities/product.entity.ts +16 -0
- package/src/domain/entities/subscription.entity.ts +24 -0
- package/src/domain/entities/sync.entity.ts +4 -0
- package/src/domain/enums/amount-type.enum.ts +10 -0
- package/src/domain/enums/billing-cycle.enum.ts +30 -0
- package/src/domain/enums/index.ts +5 -0
- package/src/domain/enums/payment-provider.enum.ts +14 -0
- package/src/domain/enums/subscription-status.enum.ts +32 -0
- package/src/domain/index.ts +5 -0
- package/src/domain/interfaces/billing-adapter.interface.ts +18 -0
- package/src/domain/interfaces/index.ts +1 -0
- package/src/domain/mappers/product.mapper.ts +176 -0
- package/src/domain/utils/minor-units.ts +20 -0
- package/src/domain/utils/percent-discount.ts +17 -0
- package/src/http.ts +3 -0
- package/src/index.ts +12 -0
- package/src/infrastructure/browser/hosted-checkout.ts +26 -0
- package/src/infrastructure/config/billing-env.ts +31 -0
- package/src/infrastructure/constants/billing.constants.ts +10 -0
- package/src/infrastructure/http/http-client.ts +83 -0
- package/src/infrastructure/pagination/page-iterator.ts +43 -0
- package/src/infrastructure/query/query-builder.ts +26 -0
- package/src/infrastructure/services/billing.client.ts +300 -0
- package/src/infrastructure/services/create-billing-client.ts +23 -0
- package/src/infrastructure/services/http/create-http-adapter.ts +123 -0
- package/src/infrastructure/services/memory/create-memory-adapter.ts +126 -0
- package/src/infrastructure/services/product.repository.ts +198 -0
- package/src/infrastructure/utils/checkout-metadata.ts +55 -0
- package/src/infrastructure/utils/date-renderer.ts +53 -0
- package/src/infrastructure/utils/money-formatter.ts +39 -0
- package/src/infrastructure/utils/normalization.util.ts +22 -0
- package/src/infrastructure/utils/pagination-clamp.ts +11 -0
- package/src/infrastructure/utils/receipt.util.ts +6 -0
- package/src/infrastructure/utils/security.util.ts +23 -0
- package/src/infrastructure/utils/type-guards.util.ts +23 -0
- package/src/infrastructure/utils/validation.util.ts +10 -0
- package/src/infrastructure/webhook/parse-event.ts +135 -0
- package/src/infrastructure/webhook/verify-webhook.ts +124 -0
- package/src/memory.ts +3 -0
- package/src/portal.ts +2 -0
- package/src/presentation/components/BillingHistoryList.tsx +172 -0
- package/src/presentation/components/BillingProvider.tsx +179 -0
- package/src/presentation/components/CancelSubscriptionButton.tsx +155 -0
- package/src/presentation/components/CheckoutButton.tsx +76 -0
- package/src/presentation/components/CustomerPortalLink.tsx +71 -0
- package/src/presentation/components/PricingTable.tsx +225 -0
- package/src/presentation/components/SubscriptionStatusBadge.tsx +102 -0
- package/src/presentation/context/billing-context.ts +30 -0
- package/src/presentation/hooks/useBilling.ts +3 -0
- package/src/presentation/hooks/useCancelSubscription.ts +47 -0
- package/src/presentation/hooks/useCheckoutButton.ts +49 -0
- package/src/presentation/hooks/useProducts.ts +76 -0
- package/src/presentation/navigation/navigation.strategy.ts +16 -0
- package/src/presentation/portal/TurnkeyBillingPage.tsx +144 -0
- package/src/presentation/portal/components/BillingLayout.tsx +163 -0
- package/src/presentation/portal/components/BillingPage.tsx +22 -0
- package/src/presentation/portal/components/BillingPortal.tsx +248 -0
- package/src/presentation/portal/components/InvoiceCard.tsx +275 -0
- package/src/presentation/portal/components/OverviewTab.tsx +137 -0
- package/src/presentation/portal/components/PaymentMethodsList.tsx +268 -0
- package/src/presentation/portal/components/PlanComparison.tsx +405 -0
- package/src/presentation/portal/components/PlanTab.tsx +197 -0
- package/src/presentation/portal/components/PortalButton.tsx +89 -0
- package/src/presentation/portal/components/PortalSpinner.tsx +30 -0
- package/src/presentation/portal/components/UsageCard.tsx +158 -0
- package/src/presentation/portal/components/index.ts +11 -0
- package/src/presentation/portal/constants.ts +44 -0
- package/src/presentation/portal/create-portal-client.ts +202 -0
- package/src/presentation/portal/hooks/index.ts +3 -0
- package/src/presentation/portal/hooks/useBilling.ts +240 -0
- package/src/presentation/portal/index.ts +97 -0
- package/src/presentation/portal/labels.tsx +218 -0
- package/src/presentation/portal/styles.ts +70 -0
- package/src/presentation/portal/types.ts +213 -0
- package/src/presentation/portal/utils/billing.ts +193 -0
- package/src/presentation/portal/utils/index.ts +27 -0
- package/src/presentation/portal/utils/plans.ts +71 -0
- package/src/presentation/theme/visual-tokens.ts +103 -0
- package/src/presentation/utils/cancellation-reason-catalog.ts +18 -0
- package/src/presentation/utils/checkout-button-visual.ts +50 -0
- package/src/presentation/utils/inline-error-visual.ts +21 -0
- package/src/presentation/utils/product-pricing.ts +33 -0
- package/src/presentation/utils/status-label.ts +23 -0
- package/src/presentation/utils/status-tone.ts +25 -0
- package/src/products.ts +20 -0
- package/src/react.tsx +63 -0
- package/src/server.ts +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 umituz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,350 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @umituz/web-payment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Universal web payment and subscription billing engine with multi-provider architecture (Polar.sh, Stripe, etc.), an HTTP adapter, a turnkey billing portal, and React bindings.
|
|
4
|
+
|
|
5
|
+
The SDK is split into an **adapter-agnostic core** (`BillingAdapter` contract, payment models, calculation primitives)
|
|
6
|
+
and a **React presentation layer** (`BillingProvider`, `useBilling`).
|
|
7
|
+
The HTTP adapter binds the contract to your own API; any payment backend can
|
|
8
|
+
be plugged in by implementing the same interface.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Multi-provider architecture with adapter-agnostic core (`BillingAdapter`, `PaymentServerClient`)
|
|
13
|
+
- Polar.sh client & webhook verification (`PolarBillingClient`, `verifyPolarWebhook`)
|
|
14
|
+
- Extensible for Stripe and future payment gateways
|
|
15
|
+
- HTTP adapter for your own REST backend (`createHttpAdapter`)
|
|
16
|
+
- Common checkout utilities (`buildCheckoutMetadata`, `readCheckoutMetadata`, `formatReceiptNumber`)
|
|
17
|
+
- Polar.sh product catalog (`getProducts`, `getProductById`, `getAllProducts`)
|
|
18
|
+
- React provider + hooks (`BillingProvider`, `useBilling`, `useProducts`)
|
|
19
|
+
- Ready-made UI: pricing table, checkout button, status badge, billing
|
|
20
|
+
history, customer portal link, cancel flow — all localizable
|
|
21
|
+
- Turnkey billing portal page (`TurnkeyBillingPage`)
|
|
22
|
+
- Strongly-typed domain models, abortable I/O
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @umituz/web-payment
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Peers are **optional** — install only what you use:
|
|
31
|
+
|
|
32
|
+
| You use… | Also install | Runtime requirement |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `…/core` | — | Any JS runtime (zero deps) |
|
|
35
|
+
| `…/products` | — | `fetch` (browsers, Node ≥ 18, workers) |
|
|
36
|
+
| `…/react` | `react` (≥ 18) | React DOM or React Native Web |
|
|
37
|
+
| root (`@umituz/web-polar-payment`) | `react` if you use the React parts | Node ≥ 18 (`engines`) |
|
|
38
|
+
|
|
39
|
+
`react-dom` is **not** required. The package is fully tree-shakeable
|
|
40
|
+
(`sideEffects: false`, ESM + CJS + `.d.ts` via tsup).
|
|
41
|
+
|
|
42
|
+
## Subpath entries
|
|
43
|
+
|
|
44
|
+
Each surface pulls in only its own dependencies — importing `/react`
|
|
45
|
+
never loads server code, importing `/core` loads nothing at all:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// Pure domain — types, enums, mappers, the BillingAdapter contract
|
|
49
|
+
import { BILLING_CYCLE, parseBillingCycle, mapProduct } from '@umituz/web-polar-payment/core';
|
|
50
|
+
|
|
51
|
+
// Polar.sh product catalog over a pluggable HttpClient
|
|
52
|
+
import { ProductRepository, FetchHttpClient } from '@umituz/web-polar-payment/products';
|
|
53
|
+
|
|
54
|
+
// React layer: provider, hooks, components, theme tokens
|
|
55
|
+
import { BillingProvider, PricingTable } from '@umituz/web-polar-payment/react';
|
|
56
|
+
|
|
57
|
+
// Server-side Polar.sh operations: checkout sessions, customer portal,
|
|
58
|
+
// subscription management, webhook signature verification (server only!)
|
|
59
|
+
import { BillingClient, verifyWebhook } from '@umituz/web-polar-payment/server';
|
|
60
|
+
|
|
61
|
+
// Browser BillingAdapter over your own REST endpoints
|
|
62
|
+
import { createHttpAdapter } from '@umituz/web-polar-payment/http';
|
|
63
|
+
|
|
64
|
+
// Turnkey billing portal page over any adapter
|
|
65
|
+
import { TurnkeyBillingPage } from '@umituz/web-polar-payment/portal';
|
|
66
|
+
|
|
67
|
+
// Root entry = everything above (backwards-compatible import paths)
|
|
68
|
+
import { useBilling } from '@umituz/web-polar-payment';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### 1. Wrap the app
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { BillingProvider, createHttpAdapter } from '@umituz/web-polar-payment';
|
|
77
|
+
|
|
78
|
+
const adapter = createHttpAdapter({
|
|
79
|
+
baseUrl: '/api/billing',
|
|
80
|
+
getUserId: () => session.organizationId,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function App({ userId, children }) {
|
|
84
|
+
return (
|
|
85
|
+
<BillingProvider adapter={adapter} userId={userId}>
|
|
86
|
+
{children}
|
|
87
|
+
</BillingProvider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Read subscription state
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { useBilling } from '@umituz/web-polar-payment';
|
|
96
|
+
|
|
97
|
+
function PlanBadge() {
|
|
98
|
+
const { status, loading } = useBilling();
|
|
99
|
+
if (loading) return null;
|
|
100
|
+
return <span>{status.plan}</span>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Start a checkout
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useBilling } from '@umituz/web-polar-payment';
|
|
108
|
+
|
|
109
|
+
function UpgradeButton({ productId }) {
|
|
110
|
+
const { startCheckout } = useBilling();
|
|
111
|
+
return (
|
|
112
|
+
<button onClick={() => startCheckout({ productId, billingCycle: 'monthly' })}>
|
|
113
|
+
Upgrade
|
|
114
|
+
</button>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. Sync after a checkout redirect
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useEffect } from 'react';
|
|
123
|
+
import { useBilling } from '@umituz/web-polar-payment';
|
|
124
|
+
|
|
125
|
+
function CheckoutReturn() {
|
|
126
|
+
const { syncSubscription } = useBilling();
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
void syncSubscription();
|
|
129
|
+
}, [syncSubscription]);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 5. Pricing table — the secure pattern
|
|
135
|
+
|
|
136
|
+
Fetch products **on your server** and pass them down; no access token ever
|
|
137
|
+
reaches the browser bundle:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// server.ts
|
|
141
|
+
import { getAllProducts } from '@umituz/web-polar-payment/products';
|
|
142
|
+
|
|
143
|
+
const products = await getAllProducts(
|
|
144
|
+
{ apiToken: process.env.POLAR_OAT_TOKEN! }, // server-side secret
|
|
145
|
+
{ isRecurring: true },
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// PricingPage.tsx
|
|
149
|
+
<PricingTable products={products} ctaLabel="Subscribe" />
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> ⚠️ OAT tokens grant full account access — never ship them to a public
|
|
153
|
+
> browser bundle. The `apiToken` prop on `PricingTable` exists for local
|
|
154
|
+
> development only.
|
|
155
|
+
|
|
156
|
+
### 6. Localize any component
|
|
157
|
+
|
|
158
|
+
Every component takes an optional `labels` subset and `locale`:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<SubscriptionStatusBadge
|
|
162
|
+
loading={false}
|
|
163
|
+
status={status}
|
|
164
|
+
locale="de-DE"
|
|
165
|
+
labels={{ statuses: { active: 'Aktiv' }, endsPrefix: 'endet' }}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 7. Own REST backend — `./server` + `./http`
|
|
170
|
+
|
|
171
|
+
When the backend is your own API worker (any runtime with `fetch`), split the
|
|
172
|
+
work with the two transport entry points:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// api/billing.ts — server side
|
|
176
|
+
import {
|
|
177
|
+
BillingClient,
|
|
178
|
+
verifyWebhook,
|
|
179
|
+
parseWebhookEvent,
|
|
180
|
+
asCheckoutCompleted,
|
|
181
|
+
asSubscriptionChanged,
|
|
182
|
+
asOrderPaid,
|
|
183
|
+
} from '@umituz/web-polar-payment/server';
|
|
184
|
+
|
|
185
|
+
const billing = new BillingClient({
|
|
186
|
+
apiToken: env.POLAR_ACCESS_TOKEN!, // OAT token, server only
|
|
187
|
+
sandbox: env.POLAR_SANDBOX === 'true', // sandbox-api.polar.sh
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Checkout: POST /v1/checkouts with `products: [id]` — metadata is copied
|
|
191
|
+
// onto the order/subscription. Never collect card data yourself: redirect
|
|
192
|
+
// the browser to `checkout.url` (Polar-hosted, PCI scope stays SAQ-A).
|
|
193
|
+
const checkout = await billing.createCheckout({
|
|
194
|
+
productId,
|
|
195
|
+
successUrl: `${appUrl}/billing/return?checkout_id={CHECKOUT_ID}`,
|
|
196
|
+
externalCustomerId: user.id, // links your user to the Polar customer
|
|
197
|
+
metadata: { organizationId: user.orgId },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Dynamic amount (bookings, pay-what-you-want floors): an ad-hoc fixed
|
|
201
|
+
// price is created on the checkout session, catalog product stays intact
|
|
202
|
+
const bookingCheckout = await billing.createCheckout({
|
|
203
|
+
productId: bookingProductId,
|
|
204
|
+
amountCents: 2500,
|
|
205
|
+
currency: 'usd',
|
|
206
|
+
allowDiscountCodes: false,
|
|
207
|
+
metadata: { bookingId },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// One-time products (digital goods): POST /v1/products with a fixed price
|
|
211
|
+
const product = await billing.createProduct({
|
|
212
|
+
name: 'Ultralight Gear Course',
|
|
213
|
+
amountCents: 7900, // smallest currency unit
|
|
214
|
+
metadata: { creatorId },
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Managed billing portal (payment methods, invoices, cancellation)
|
|
218
|
+
const { url } = await billing.createPortalSession(customerId, appUrl);
|
|
219
|
+
|
|
220
|
+
// Cancel at period end (reversible) vs revoke (immediate)
|
|
221
|
+
await billing.updateSubscription(subscriptionId, { cancelAtPeriodEnd: true });
|
|
222
|
+
|
|
223
|
+
// Webhooks: Standard Webhooks spec — verify the RAW body, before JSON.parse
|
|
224
|
+
const raw = await request.text();
|
|
225
|
+
if (!(await verifyWebhook(raw, request.headers, env.POLAR_WEBHOOK_SECRET))) {
|
|
226
|
+
return new Response('invalid signature', { status: 401 });
|
|
227
|
+
}
|
|
228
|
+
// Then parse the (still raw) body into a typed event view
|
|
229
|
+
const event = parseWebhookEvent(raw);
|
|
230
|
+
const sale = event && asCheckoutCompleted(event); // checkout.completed
|
|
231
|
+
const change = event && asSubscriptionChanged(event); // subscription.* lifecycle
|
|
232
|
+
const paid = event && asOrderPaid(event); // order.paid (renewals)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The browser side binds the same `BillingAdapter` contract to six endpoints under
|
|
236
|
+
one base path — implement them on your worker to match:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
// app/billing.ts — browser side
|
|
240
|
+
import { createHttpAdapter } from '@umituz/web-polar-payment/http';
|
|
241
|
+
|
|
242
|
+
const adapter = createHttpAdapter({
|
|
243
|
+
baseUrl: '/api/billing',
|
|
244
|
+
getUserId: () => session.organizationId, // used when a call has no id
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// GET /status?userId= POST /checkout { productId, billingCycle?, ... }
|
|
248
|
+
// POST /sync { checkoutId? } GET /history?userId=
|
|
249
|
+
// POST /cancel { reason? } GET /portal?userId=
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Failures return a `{ "error": string | { "message": string } }` envelope with
|
|
253
|
+
a non-2xx status; the adapter surfaces it as `Error("detail (HTTP status)")`.
|
|
254
|
+
|
|
255
|
+
### 8. Turnkey billing page — `./portal` over any adapter
|
|
256
|
+
|
|
257
|
+
`TurnkeyBillingPage` composes the whole portal (plan comparison, subscription
|
|
258
|
+
tabs, invoices, hosted-portal payment methods) over a `BillingAdapter`. The
|
|
259
|
+
host binds the adapter and the server-fetched catalog; everything else —
|
|
260
|
+
data loading, checkout redirects, cancellation — is wired inside:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { TurnkeyBillingPage } from '@umituz/web-polar-payment/portal';
|
|
264
|
+
import { createHttpAdapter } from '@umituz/web-polar-payment/http';
|
|
265
|
+
|
|
266
|
+
// products: fetched from your server (metadata.planId tags plan keys)
|
|
267
|
+
<TurnkeyBillingPage
|
|
268
|
+
adapter={createHttpAdapter({ baseUrl: '/api/billing', getUserId: () => orgId })}
|
|
269
|
+
products={products}
|
|
270
|
+
config={{ brandName: 'Acme', supportEmail: 'billing@acme.dev' }}
|
|
271
|
+
/>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Prefer composing yourself? `createPortalClient({ adapter, products })`
|
|
275
|
+
returns the portal's `BillingApiClient` seam (plus `selectPlan` /
|
|
276
|
+
`openPortal`) for direct use with `useBilling`.
|
|
277
|
+
|
|
278
|
+
## Error handling
|
|
279
|
+
|
|
280
|
+
Server-side failures throw `BillingApiError` with the HTTP `status` and the
|
|
281
|
+
raw `body` attached — check with `isBillingApiError(error)`. Browser-side
|
|
282
|
+
adapter failures surface as plain `Error` values carrying the endpoint's
|
|
283
|
+
`{ "error": ... }` envelope detail and the HTTP status.
|
|
284
|
+
|
|
285
|
+
## Architecture
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
src/
|
|
289
|
+
├── core.ts / products.ts / react.tsx # subpath entries (+ server, http, portal)
|
|
290
|
+
├── domain/ # Pure types + interfaces (no I/O)
|
|
291
|
+
│ ├── entities/ # Subscription, Checkout, Order, ...
|
|
292
|
+
│ ├── enums/ # SubscriptionStatus, BillingCycle, ...
|
|
293
|
+
│ ├── interfaces/ # BillingAdapter contract
|
|
294
|
+
│ ├── utils/ # minor-unit money math
|
|
295
|
+
│ └── mappers/ # snake_case DTO → camelCase domain
|
|
296
|
+
├── infrastructure/ # Transport, repositories, utilities
|
|
297
|
+
│ ├── http/ # HttpClient (Strategy, AbortSignal-aware)
|
|
298
|
+
│ ├── query/ # QueryBuilder (Builder)
|
|
299
|
+
│ ├── pagination/ # PageIterator (Iterator)
|
|
300
|
+
│ ├── services/ # ProductRepository, BillingClient,
|
|
301
|
+
│ │ └── http/ # createHttpAdapter (six-endpoint contract)
|
|
302
|
+
│ ├── constants/ # SUBSCRIPTION_STATUS, API base URL
|
|
303
|
+
│ └── utils/ # validation, security, type-guards
|
|
304
|
+
└── presentation/ # React layer
|
|
305
|
+
├── context/ # BillingContext (race-safe refresh)
|
|
306
|
+
├── hooks/ # useBilling, useProducts
|
|
307
|
+
├── navigation/ # NavigationStrategy
|
|
308
|
+
├── portal/ # Turnkey billing portal (page + client)
|
|
309
|
+
├── theme/ # VisualTokens (self-contained)
|
|
310
|
+
└── components/ # Provider + UI components
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Patterns in use
|
|
314
|
+
|
|
315
|
+
- **Adapter** — `BillingAdapter` interface, HTTP implementation
|
|
316
|
+
- **Repository** — one repository per business operation
|
|
317
|
+
- **Strategy** — `HttpClient` (swap `fetch` for tests), `NavigationStrategy`
|
|
318
|
+
- **Builder** — `QueryBuilder` for URL search params
|
|
319
|
+
- **Iterator** — `PageIterator` for paginated Polar.sh lists
|
|
320
|
+
- **Error as a value** — `BillingApiError` carries status + raw body
|
|
321
|
+
|
|
322
|
+
### Design decisions
|
|
323
|
+
|
|
324
|
+
- **Dependency direction points inward.** `presentation` and
|
|
325
|
+
`infrastructure` depend on `domain`; `domain` depends on nothing. This is
|
|
326
|
+
what lets each subpath ship with only its own dependencies.
|
|
327
|
+
- **VisualTokens are self-contained** rather than depending on a shared
|
|
328
|
+
design-system package: a billing SDK must be droppable into any app
|
|
329
|
+
without dragging a UI framework along. Override styles via `labels`,
|
|
330
|
+
`locale`, and component composition instead.
|
|
331
|
+
- **Localization via prop injection, not an i18n framework** — `labels`
|
|
332
|
+
objects with English defaults keep the runtime dependency-free while
|
|
333
|
+
still allowing full translation.
|
|
334
|
+
- **Optimistic-free state**: `BillingProvider` serializes refreshes behind a
|
|
335
|
+
generation counter; a slow earlier response can never overwrite a newer
|
|
336
|
+
one.
|
|
337
|
+
|
|
338
|
+
## Development
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
npm run typecheck # tsc --noEmit
|
|
342
|
+
npm run lint # eslint (flat config)
|
|
343
|
+
npm test # vitest — 170 tests (domain, infra, presentation)
|
|
344
|
+
npm run build # tsup — CJS + ESM + d.ts for every subpath entry
|
|
345
|
+
npm pack --dry-run # inspect the publishable tarball
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## License
|
|
349
|
+
|
|
350
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { b as BillingCycle } from './billing-cycle.enum--hJRYlgP.mjs';
|
|
2
|
+
|
|
3
|
+
declare const SUBSCRIPTION_STATUS: {
|
|
4
|
+
readonly ACTIVE: "active";
|
|
5
|
+
readonly CANCELED: "canceled";
|
|
6
|
+
readonly REVOKED: "revoked";
|
|
7
|
+
readonly TRIALING: "trialing";
|
|
8
|
+
readonly PAST_DUE: "past_due";
|
|
9
|
+
readonly INCOMPLETE: "incomplete";
|
|
10
|
+
readonly INCOMPLETE_EXPIRED: "incomplete_expired";
|
|
11
|
+
readonly UNPAID: "unpaid";
|
|
12
|
+
readonly NONE: "none";
|
|
13
|
+
};
|
|
14
|
+
declare const SUBSCRIPTION_STATUS_VALUES: readonly ["active", "canceled", "revoked", "trialing", "past_due", "incomplete", "incomplete_expired", "unpaid", "none"];
|
|
15
|
+
type SubscriptionStatusValue = (typeof SUBSCRIPTION_STATUS_VALUES)[number];
|
|
16
|
+
declare function isKnownSubscriptionStatus(value: unknown): value is SubscriptionStatusValue;
|
|
17
|
+
|
|
18
|
+
declare const CANCELLATION_REASON: Readonly<{
|
|
19
|
+
readonly TOO_EXPENSIVE: "too_expensive";
|
|
20
|
+
readonly MISSING_FEATURES: "missing_features";
|
|
21
|
+
readonly SWITCHED_SERVICE: "switched_service";
|
|
22
|
+
readonly UNUSED: "unused";
|
|
23
|
+
readonly CUSTOMER_SERVICE: "customer_service";
|
|
24
|
+
readonly LOW_QUALITY: "low_quality";
|
|
25
|
+
readonly TOO_COMPLEX: "too_complex";
|
|
26
|
+
readonly OTHER: "other";
|
|
27
|
+
}>;
|
|
28
|
+
declare const CANCELLATION_REASON_VALUES: readonly ["too_expensive", "missing_features", "switched_service", "unused", "customer_service", "low_quality", "too_complex", "other"];
|
|
29
|
+
type CancellationReason = (typeof CANCELLATION_REASON_VALUES)[number];
|
|
30
|
+
interface CancelResult {
|
|
31
|
+
success: boolean;
|
|
32
|
+
endsAt?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SubscriptionStatus {
|
|
36
|
+
plan: string;
|
|
37
|
+
subscriptionId?: string;
|
|
38
|
+
subscriptionStatus: SubscriptionStatusValue;
|
|
39
|
+
cancelAtPeriodEnd?: boolean;
|
|
40
|
+
currentPeriodEnd?: string;
|
|
41
|
+
billingCycle?: BillingCycle;
|
|
42
|
+
providerCustomerId?: string;
|
|
43
|
+
}
|
|
44
|
+
declare const FREE_SUBSCRIPTION_STATUS: SubscriptionStatus;
|
|
45
|
+
declare function isActiveSubscription(status: SubscriptionStatus): boolean;
|
|
46
|
+
|
|
47
|
+
interface CheckoutParams {
|
|
48
|
+
productId?: string;
|
|
49
|
+
productKey?: string;
|
|
50
|
+
itemId?: string;
|
|
51
|
+
couponCode?: string;
|
|
52
|
+
planKey?: string;
|
|
53
|
+
billingCycle?: BillingCycle;
|
|
54
|
+
successUrl?: string;
|
|
55
|
+
userId?: string;
|
|
56
|
+
customerEmail?: string;
|
|
57
|
+
seats?: number;
|
|
58
|
+
metadata?: Record<string, string | number | boolean>;
|
|
59
|
+
}
|
|
60
|
+
interface CheckoutResult {
|
|
61
|
+
url: string;
|
|
62
|
+
id: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface OrderItem {
|
|
66
|
+
id: string;
|
|
67
|
+
createdAt: string;
|
|
68
|
+
amount: number;
|
|
69
|
+
currency: string;
|
|
70
|
+
status: string;
|
|
71
|
+
paid: boolean;
|
|
72
|
+
productName?: string;
|
|
73
|
+
itemType?: string;
|
|
74
|
+
itemId?: string;
|
|
75
|
+
amountUsd?: number;
|
|
76
|
+
receiptNumber?: string;
|
|
77
|
+
invoiceUrl?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface SyncResult {
|
|
81
|
+
synced: boolean;
|
|
82
|
+
plan?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface BillingAdapter {
|
|
86
|
+
getStatus(userId: string): Promise<SubscriptionStatus>;
|
|
87
|
+
createCheckout(params: CheckoutParams): Promise<CheckoutResult>;
|
|
88
|
+
syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult>;
|
|
89
|
+
getBillingHistory(userId: string): Promise<OrderItem[]>;
|
|
90
|
+
cancelSubscription(reason?: CancellationReason): Promise<CancelResult>;
|
|
91
|
+
getPortalUrl(userId: string): Promise<string>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { type BillingAdapter as B, CANCELLATION_REASON as C, FREE_SUBSCRIPTION_STATUS as F, type OrderItem as O, SUBSCRIPTION_STATUS as S, CANCELLATION_REASON_VALUES as a, type CancelResult as b, type CancellationReason as c, type CheckoutParams as d, type CheckoutResult as e, SUBSCRIPTION_STATUS_VALUES as f, type SubscriptionStatus as g, type SubscriptionStatusValue as h, type SyncResult as i, isActiveSubscription as j, isKnownSubscriptionStatus as k };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { b as BillingCycle } from './billing-cycle.enum--hJRYlgP.js';
|
|
2
|
+
|
|
3
|
+
declare const SUBSCRIPTION_STATUS: {
|
|
4
|
+
readonly ACTIVE: "active";
|
|
5
|
+
readonly CANCELED: "canceled";
|
|
6
|
+
readonly REVOKED: "revoked";
|
|
7
|
+
readonly TRIALING: "trialing";
|
|
8
|
+
readonly PAST_DUE: "past_due";
|
|
9
|
+
readonly INCOMPLETE: "incomplete";
|
|
10
|
+
readonly INCOMPLETE_EXPIRED: "incomplete_expired";
|
|
11
|
+
readonly UNPAID: "unpaid";
|
|
12
|
+
readonly NONE: "none";
|
|
13
|
+
};
|
|
14
|
+
declare const SUBSCRIPTION_STATUS_VALUES: readonly ["active", "canceled", "revoked", "trialing", "past_due", "incomplete", "incomplete_expired", "unpaid", "none"];
|
|
15
|
+
type SubscriptionStatusValue = (typeof SUBSCRIPTION_STATUS_VALUES)[number];
|
|
16
|
+
declare function isKnownSubscriptionStatus(value: unknown): value is SubscriptionStatusValue;
|
|
17
|
+
|
|
18
|
+
declare const CANCELLATION_REASON: Readonly<{
|
|
19
|
+
readonly TOO_EXPENSIVE: "too_expensive";
|
|
20
|
+
readonly MISSING_FEATURES: "missing_features";
|
|
21
|
+
readonly SWITCHED_SERVICE: "switched_service";
|
|
22
|
+
readonly UNUSED: "unused";
|
|
23
|
+
readonly CUSTOMER_SERVICE: "customer_service";
|
|
24
|
+
readonly LOW_QUALITY: "low_quality";
|
|
25
|
+
readonly TOO_COMPLEX: "too_complex";
|
|
26
|
+
readonly OTHER: "other";
|
|
27
|
+
}>;
|
|
28
|
+
declare const CANCELLATION_REASON_VALUES: readonly ["too_expensive", "missing_features", "switched_service", "unused", "customer_service", "low_quality", "too_complex", "other"];
|
|
29
|
+
type CancellationReason = (typeof CANCELLATION_REASON_VALUES)[number];
|
|
30
|
+
interface CancelResult {
|
|
31
|
+
success: boolean;
|
|
32
|
+
endsAt?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SubscriptionStatus {
|
|
36
|
+
plan: string;
|
|
37
|
+
subscriptionId?: string;
|
|
38
|
+
subscriptionStatus: SubscriptionStatusValue;
|
|
39
|
+
cancelAtPeriodEnd?: boolean;
|
|
40
|
+
currentPeriodEnd?: string;
|
|
41
|
+
billingCycle?: BillingCycle;
|
|
42
|
+
providerCustomerId?: string;
|
|
43
|
+
}
|
|
44
|
+
declare const FREE_SUBSCRIPTION_STATUS: SubscriptionStatus;
|
|
45
|
+
declare function isActiveSubscription(status: SubscriptionStatus): boolean;
|
|
46
|
+
|
|
47
|
+
interface CheckoutParams {
|
|
48
|
+
productId?: string;
|
|
49
|
+
productKey?: string;
|
|
50
|
+
itemId?: string;
|
|
51
|
+
couponCode?: string;
|
|
52
|
+
planKey?: string;
|
|
53
|
+
billingCycle?: BillingCycle;
|
|
54
|
+
successUrl?: string;
|
|
55
|
+
userId?: string;
|
|
56
|
+
customerEmail?: string;
|
|
57
|
+
seats?: number;
|
|
58
|
+
metadata?: Record<string, string | number | boolean>;
|
|
59
|
+
}
|
|
60
|
+
interface CheckoutResult {
|
|
61
|
+
url: string;
|
|
62
|
+
id: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface OrderItem {
|
|
66
|
+
id: string;
|
|
67
|
+
createdAt: string;
|
|
68
|
+
amount: number;
|
|
69
|
+
currency: string;
|
|
70
|
+
status: string;
|
|
71
|
+
paid: boolean;
|
|
72
|
+
productName?: string;
|
|
73
|
+
itemType?: string;
|
|
74
|
+
itemId?: string;
|
|
75
|
+
amountUsd?: number;
|
|
76
|
+
receiptNumber?: string;
|
|
77
|
+
invoiceUrl?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface SyncResult {
|
|
81
|
+
synced: boolean;
|
|
82
|
+
plan?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface BillingAdapter {
|
|
86
|
+
getStatus(userId: string): Promise<SubscriptionStatus>;
|
|
87
|
+
createCheckout(params: CheckoutParams): Promise<CheckoutResult>;
|
|
88
|
+
syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult>;
|
|
89
|
+
getBillingHistory(userId: string): Promise<OrderItem[]>;
|
|
90
|
+
cancelSubscription(reason?: CancellationReason): Promise<CancelResult>;
|
|
91
|
+
getPortalUrl(userId: string): Promise<string>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { type BillingAdapter as B, CANCELLATION_REASON as C, FREE_SUBSCRIPTION_STATUS as F, type OrderItem as O, SUBSCRIPTION_STATUS as S, CANCELLATION_REASON_VALUES as a, type CancelResult as b, type CancellationReason as c, type CheckoutParams as d, type CheckoutResult as e, SUBSCRIPTION_STATUS_VALUES as f, type SubscriptionStatus as g, type SubscriptionStatusValue as h, type SyncResult as i, isActiveSubscription as j, isKnownSubscriptionStatus as k };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const BILLING_CYCLE: Readonly<{
|
|
2
|
+
readonly MONTHLY: "monthly";
|
|
3
|
+
readonly YEARLY: "yearly";
|
|
4
|
+
}>;
|
|
5
|
+
declare const BILLING_CYCLE_VALUES: readonly ["monthly", "yearly"];
|
|
6
|
+
type BillingCycle = (typeof BILLING_CYCLE_VALUES)[number];
|
|
7
|
+
declare function isKnownBillingCycle(value: unknown): value is BillingCycle;
|
|
8
|
+
declare function parseBillingCycle(value: unknown): BillingCycle | null;
|
|
9
|
+
|
|
10
|
+
export { BILLING_CYCLE as B, BILLING_CYCLE_VALUES as a, type BillingCycle as b, isKnownBillingCycle as i, parseBillingCycle as p };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const BILLING_CYCLE: Readonly<{
|
|
2
|
+
readonly MONTHLY: "monthly";
|
|
3
|
+
readonly YEARLY: "yearly";
|
|
4
|
+
}>;
|
|
5
|
+
declare const BILLING_CYCLE_VALUES: readonly ["monthly", "yearly"];
|
|
6
|
+
type BillingCycle = (typeof BILLING_CYCLE_VALUES)[number];
|
|
7
|
+
declare function isKnownBillingCycle(value: unknown): value is BillingCycle;
|
|
8
|
+
declare function parseBillingCycle(value: unknown): BillingCycle | null;
|
|
9
|
+
|
|
10
|
+
export { BILLING_CYCLE as B, BILLING_CYCLE_VALUES as a, type BillingCycle as b, isKnownBillingCycle as i, parseBillingCycle as p };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const CHECKOUT_ID_PARAM = "checkout_id";
|
|
2
|
+
declare const CHECKOUT_ID_PLACEHOLDER = "{CHECKOUT_ID}";
|
|
3
|
+
type ProductIdMap = Map<string, string>;
|
|
4
|
+
declare function parseProductIds(raw: string | undefined): ProductIdMap;
|
|
5
|
+
declare function productIdOf(ids: ProductIdMap, productKey: string): string | null;
|
|
6
|
+
declare function successUrlWithCheckoutId(origin: string, returnPath: string): string;
|
|
7
|
+
|
|
8
|
+
export { CHECKOUT_ID_PARAM as C, type ProductIdMap as P, CHECKOUT_ID_PLACEHOLDER as a, productIdOf as b, parseProductIds as p, successUrlWithCheckoutId as s };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const CHECKOUT_ID_PARAM = "checkout_id";
|
|
2
|
+
declare const CHECKOUT_ID_PLACEHOLDER = "{CHECKOUT_ID}";
|
|
3
|
+
type ProductIdMap = Map<string, string>;
|
|
4
|
+
declare function parseProductIds(raw: string | undefined): ProductIdMap;
|
|
5
|
+
declare function productIdOf(ids: ProductIdMap, productKey: string): string | null;
|
|
6
|
+
declare function successUrlWithCheckoutId(origin: string, returnPath: string): string;
|
|
7
|
+
|
|
8
|
+
export { CHECKOUT_ID_PARAM as C, type ProductIdMap as P, CHECKOUT_ID_PLACEHOLDER as a, productIdOf as b, parseProductIds as p, successUrlWithCheckoutId as s };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare const PAYMENT_PROVIDER: {
|
|
2
|
+
readonly POLAR: "polar";
|
|
3
|
+
readonly STRIPE: "stripe";
|
|
4
|
+
};
|
|
5
|
+
declare const PAYMENT_PROVIDER_VALUES: ("polar" | "stripe")[];
|
|
6
|
+
type PaymentProvider = (typeof PAYMENT_PROVIDER)[keyof typeof PAYMENT_PROVIDER];
|
|
7
|
+
declare function isKnownPaymentProvider(value: unknown): value is PaymentProvider;
|
|
8
|
+
|
|
9
|
+
declare function formatReceiptNumber(id: string, prefix?: string): string;
|
|
10
|
+
|
|
11
|
+
interface CheckoutOrderMetadata {
|
|
12
|
+
userId: string;
|
|
13
|
+
itemType: string;
|
|
14
|
+
itemId: string;
|
|
15
|
+
couponCode?: string;
|
|
16
|
+
}
|
|
17
|
+
interface BuildCheckoutMetadataInput {
|
|
18
|
+
userId: string;
|
|
19
|
+
itemType: string;
|
|
20
|
+
itemId: string;
|
|
21
|
+
couponCode?: string;
|
|
22
|
+
extra?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
declare function buildCheckoutMetadata(input: BuildCheckoutMetadataInput): Record<string, string>;
|
|
25
|
+
declare function readCheckoutMetadata(metadata: Record<string, unknown> | undefined, allowedItemTypes: readonly string[]): CheckoutOrderMetadata | null;
|
|
26
|
+
|
|
27
|
+
export { type BuildCheckoutMetadataInput as B, type CheckoutOrderMetadata as C, PAYMENT_PROVIDER as P, PAYMENT_PROVIDER_VALUES as a, type PaymentProvider as b, buildCheckoutMetadata as c, formatReceiptNumber as f, isKnownPaymentProvider as i, readCheckoutMetadata as r };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare const PAYMENT_PROVIDER: {
|
|
2
|
+
readonly POLAR: "polar";
|
|
3
|
+
readonly STRIPE: "stripe";
|
|
4
|
+
};
|
|
5
|
+
declare const PAYMENT_PROVIDER_VALUES: ("polar" | "stripe")[];
|
|
6
|
+
type PaymentProvider = (typeof PAYMENT_PROVIDER)[keyof typeof PAYMENT_PROVIDER];
|
|
7
|
+
declare function isKnownPaymentProvider(value: unknown): value is PaymentProvider;
|
|
8
|
+
|
|
9
|
+
declare function formatReceiptNumber(id: string, prefix?: string): string;
|
|
10
|
+
|
|
11
|
+
interface CheckoutOrderMetadata {
|
|
12
|
+
userId: string;
|
|
13
|
+
itemType: string;
|
|
14
|
+
itemId: string;
|
|
15
|
+
couponCode?: string;
|
|
16
|
+
}
|
|
17
|
+
interface BuildCheckoutMetadataInput {
|
|
18
|
+
userId: string;
|
|
19
|
+
itemType: string;
|
|
20
|
+
itemId: string;
|
|
21
|
+
couponCode?: string;
|
|
22
|
+
extra?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
declare function buildCheckoutMetadata(input: BuildCheckoutMetadataInput): Record<string, string>;
|
|
25
|
+
declare function readCheckoutMetadata(metadata: Record<string, unknown> | undefined, allowedItemTypes: readonly string[]): CheckoutOrderMetadata | null;
|
|
26
|
+
|
|
27
|
+
export { type BuildCheckoutMetadataInput as B, type CheckoutOrderMetadata as C, PAYMENT_PROVIDER as P, PAYMENT_PROVIDER_VALUES as a, type PaymentProvider as b, buildCheckoutMetadata as c, formatReceiptNumber as f, isKnownPaymentProvider as i, readCheckoutMetadata as r };
|