@owlmeans/web-payment 0.1.18-rc.11 → 0.1.18-rc.12
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/README.md +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-payment/SKILL.md +70 -20
- package/build/@/components/ui/select.d.ts +8 -0
- package/build/@/components/ui/select.d.ts.map +1 -0
- package/build/@/components/ui/select.js +9 -0
- package/build/@/components/ui/select.js.map +1 -0
- package/build/dialog.d.ts +1 -1
- package/build/dialog.d.ts.map +1 -1
- package/build/dialog.js +5 -2
- package/build/dialog.js.map +1 -1
- package/build/estimate-cache.d.ts +14 -0
- package/build/estimate-cache.d.ts.map +1 -0
- package/build/estimate-cache.js +39 -0
- package/build/estimate-cache.js.map +1 -0
- package/build/estimate-summary.d.ts +56 -0
- package/build/estimate-summary.d.ts.map +1 -0
- package/build/estimate-summary.js +91 -0
- package/build/estimate-summary.js.map +1 -0
- package/build/estimate.d.ts +28 -0
- package/build/estimate.d.ts.map +1 -0
- package/build/estimate.js +67 -0
- package/build/estimate.js.map +1 -0
- package/build/i18n/be.json +1 -1
- package/build/i18n/de.json +1 -1
- package/build/i18n/en.json +1 -1
- package/build/i18n/es.json +1 -1
- package/build/i18n/pl.json +1 -1
- package/build/i18n/ru.json +1 -1
- package/build/i18n/uk.json +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/types.d.ts +23 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/@/components/ui/select.tsx +62 -0
- package/src/dialog.tsx +5 -2
- package/src/estimate-cache.ts +40 -0
- package/src/estimate-summary.tsx +162 -0
- package/src/estimate.ts +93 -0
- package/src/i18n/be.json +1 -1
- package/src/i18n/de.json +1 -1
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +1 -1
- package/src/i18n/pl.json +1 -1
- package/src/i18n/ru.json +1 -1
- package/src/i18n/uk.json +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +27 -1
- package/tests/context.ts +2 -1
- package/tests/dialog.spec.ts +49 -0
- package/tests/estimate-cache.spec.ts +46 -0
- package/tests/harness/entitlement.tsx +43 -4
- package/tests/harness/estimate-fixtures.ts +48 -0
- package/tests/harness/mount.tsx +3 -0
- package/tests/pieces.spec.ts +22 -0
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
65
65
|
your project's skill store (`.agents/skills/`):
|
|
66
66
|
|
|
67
67
|
```sh
|
|
68
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
68
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.29
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-payment",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.12",
|
|
5
|
+
"generatedAt": "2026-09-18T19:03:53.990Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -7,7 +7,7 @@ user-invocable: false
|
|
|
7
7
|
|
|
8
8
|
# @owlmeans/web-payment
|
|
9
9
|
|
|
10
|
-
**Install:** `bun add @owlmeans/web-payment@^0.1.18-rc.
|
|
10
|
+
**Install:** `bun add @owlmeans/web-payment@^0.1.18-rc.12`
|
|
11
11
|
|
|
12
12
|
Public MIT web package paired with `@owlmeans/server-payment`. It incorporates checkout, balance,
|
|
13
13
|
entitlement and shallow-auth helpers over the underlying `@owlmeans/client-payment` service, plus
|
|
@@ -82,25 +82,73 @@ and action text come from the application). Every one accepts `className`.
|
|
|
82
82
|
|
|
83
83
|
## AmountCheckoutDialog
|
|
84
84
|
|
|
85
|
-
The dialog takes `{ open, onOpenChange, policy, pending, onConfirm }`. It keeps integer
|
|
86
|
-
accepts locale decimal separators and at most two decimals, shows configured presets
|
|
87
|
-
input, and reports below/above-bound errors without clamping. Confirmation is disabled
|
|
88
|
-
or pending.
|
|
85
|
+
The dialog takes `{ open, onOpenChange, policy, pending, onConfirm, estimate? }`. It keeps integer
|
|
86
|
+
minor units, accepts locale decimal separators and at most two decimals, shows configured presets
|
|
87
|
+
plus custom input, and reports below/above-bound errors without clamping. Confirmation is disabled
|
|
88
|
+
when invalid or pending.
|
|
89
89
|
|
|
90
|
-
It shows net credit value, processing adjustment and pre-tax subtotal from `chargeAmountMinor
|
|
91
|
-
the note that Stripe calculates applicable tax
|
|
92
|
-
|
|
90
|
+
It shows net credit value, processing adjustment and pre-tax subtotal from `chargeAmountMinor`.
|
|
91
|
+
With no `estimate`, the plain note that Stripe calculates applicable tax; with one, `PriceEstimateSummary`
|
|
92
|
+
renders instead (see below) against the SAME `chargeMinor`, so the country picker and its numbers
|
|
93
|
+
track whatever the buyer has typed. The policy maximum bounds net credit, not the adjusted or
|
|
94
|
+
tax-inclusive debit.
|
|
95
|
+
|
|
96
|
+
## Price estimate: a live tax/currency read for any amount
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// self-contained (one estimate, its own picker) — the credit dialog:
|
|
100
|
+
const estimate = usePriceEstimate(ctx.entrypoint(account.priceEstimate), { enabled: dialogOpen })
|
|
101
|
+
<AmountCheckoutDialog … estimate={estimate} />
|
|
102
|
+
<PriceEstimateSummary control={estimate} subtotalMinor={planPriceMinor} currency="usd" />
|
|
103
|
+
|
|
104
|
+
// shared (one picker, several estimates) — a plan comparison table:
|
|
105
|
+
const [country, setCountry] = useState('')
|
|
106
|
+
const pro = usePriceEstimate(entry, { enabled, country, onCountryChange: setCountry }, { body: { planSku: 'pro' } })
|
|
107
|
+
const team = usePriceEstimate(entry, { enabled, country, onCountryChange: setCountry }, { body: { planSku: 'team' } })
|
|
108
|
+
<CountrySelect value={country} onChange={setCountry} /> {/* rendered ONCE */}
|
|
109
|
+
<PriceEstimateAmount control={pro} subtotalMinor={proPriceMinor} currency="usd" /> {/* per offer */}
|
|
110
|
+
<PriceEstimateAmount control={team} subtotalMinor={teamPriceMinor} currency="usd" />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- **`usePriceEstimate(entry, { enabled, ttlMs?, country?, onCountryChange? }, ...request)`** calls a
|
|
114
|
+
`PriceEstimateBody` protocol (`@owlmeans/payment`'s `PriceEstimate` response) only while `enabled`,
|
|
115
|
+
and again whenever the country changes — a closed dialog costs nothing. A module-level client
|
|
116
|
+
cache (keyed by the entry's alias, the request body and the country; default TTL 5 min) shares an
|
|
117
|
+
answer across every mounted instance and never caches a rejected fetch. The caller's `request`
|
|
118
|
+
omits `body.country` — the hook owns it — the same way `usePortal`'s omits `flow`.
|
|
119
|
+
- **Uncontrolled** (no `opts.country`): the hook owns the country itself, and the first answer
|
|
120
|
+
whose `source` is `'customer'` preselects it, once.
|
|
121
|
+
- **Controlled** (`opts.country` given): the caller owns the country — several `usePriceEstimate`
|
|
122
|
+
calls can share ONE value (a plan comparison table, one picker, one estimate per offer); the
|
|
123
|
+
hook never preselects on its own, and the returned control's `onCountryChange` calls
|
|
124
|
+
`opts.onCountryChange` instead of an internal setter.
|
|
125
|
+
- **`CountrySelect({ value, onChange, label?, id?, className? })`** is the picker alone —
|
|
126
|
+
`Intl.DisplayNames` + `Intl.Collator` over `@owlmeans/payment`'s `COUNTRY_CODES`, so it lists the
|
|
127
|
+
buyer's language's own country names. Render it once and drive every estimate that shares it.
|
|
128
|
+
- **`PriceEstimateAmount({ control, subtotalMinor, currency, suffix?, className? })`** is the
|
|
129
|
+
numbers alone, no picker: via `estimateOf` re-derived against `subtotalMinor`, a rate row for a
|
|
130
|
+
`taxed` status, an estimated total, and one sentence for every status that leaves no number to
|
|
131
|
+
trust (reverse charge, no tax, "at checkout", "choose a country"). `data-price-estimate` /
|
|
132
|
+
`data-status` are stable test hooks.
|
|
133
|
+
- When the estimate carries `local` (Adaptive Pricing found the country's currency), the tax and
|
|
134
|
+
total show ONLY in that currency, each marked `≈` — never both currencies at once, which would
|
|
135
|
+
read as two different prices for the same line. A short note explains the amount is converted
|
|
136
|
+
at Stripe's current rate and confirmed at checkout. With no `local`, the amounts show in
|
|
137
|
+
`currency` (the integration currency) with no `≈` and no note.
|
|
138
|
+
- **`PriceEstimateSummary`** is `CountrySelect` bound to the control's own `country`/
|
|
139
|
+
`onCountryChange` followed by `PriceEstimateAmount` — the one-estimate composition. Building a
|
|
140
|
+
shared picker composes the two pieces separately instead, one `PriceEstimateAmount` per estimate.
|
|
93
141
|
|
|
94
142
|
## The `@/components/ui/*` contract
|
|
95
143
|
|
|
96
144
|
Components import the consuming app's shadcn primitives through the app alias:
|
|
97
|
-
`@/components/ui/{button,dialog,input,label}` for the dialog and
|
|
145
|
+
`@/components/ui/{button,dialog,input,label,select}` for the dialog and estimate summary and
|
|
98
146
|
`@/components/ui/{card,progress,button}` for the pieces (`Card`, `CardHeader`, `CardTitle`,
|
|
99
147
|
`CardDescription`, `CardContent`, `CardFooter`, `Progress`). The consumer therefore provides those
|
|
100
|
-
files and the
|
|
101
|
-
standalone copies under `src/@` only so its own build and tests resolve
|
|
102
|
-
never exported. Styling uses semantic theme tokens only (`primary`,
|
|
103
|
-
`destructive`, `border`, `card`).
|
|
148
|
+
files and the peers `@radix-ui/react-progress` / `@radix-ui/react-select` beside the dialog's Radix
|
|
149
|
+
ones. The package carries standalone copies under `src/@` only so its own build and tests resolve
|
|
150
|
+
the same alias; they are never exported. Styling uses semantic theme tokens only (`primary`,
|
|
151
|
+
`muted-foreground`, `destructive`, `border`, `card`).
|
|
104
152
|
|
|
105
153
|
A Tailwind v4 consumer scanning package source adds:
|
|
106
154
|
|
|
@@ -111,15 +159,17 @@ A Tailwind v4 consumer scanning package source adds:
|
|
|
111
159
|
## Strings
|
|
112
160
|
|
|
113
161
|
All visible strings live in the `web-payment` library resource (`lib` namespace) in all seven
|
|
114
|
-
languages — `en pl ru be uk es de` — under
|
|
115
|
-
(`plan`, `status`, `limit`, `promo`, `capability`)
|
|
162
|
+
languages — `en pl ru be uk es de` — under three branches: `amount-checkout`, `entitlement`
|
|
163
|
+
(`plan`, `status`, `limit`, `promo`, `capability`) and `estimate` (`country`, `tax-type`, `rate`,
|
|
164
|
+
`total`, `converted-note`, `status`, `loading`, `failed`). A new key lands in every language at once, with
|
|
116
165
|
the same `{{placeholders}}`; the parity spec walks the resources recursively. An application
|
|
117
166
|
overrides a string with `addI18nApp(lng, 'web-payment', data, { ns: LIB_NAMESPACE })`.
|
|
118
167
|
|
|
119
168
|
## Tests
|
|
120
169
|
|
|
121
|
-
Category D plus unit specs: `bun test ./tests` runs the selector and
|
|
122
|
-
`react-dom/server`, a fake `window` for navigation) and drives chromium through the
|
|
123
|
-
which picks a surface by `?case=` (none = the dialog, `pieces`, `hook`)
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
Category D plus unit specs: `bun test ./tests` runs the selector, hook and cache specs (hooks
|
|
171
|
+
rendered with `react-dom/server`, a fake `window` for navigation) and drives chromium through the
|
|
172
|
+
Vite harness, which picks a surface by `?case=` (none = the dialog, `pieces`, `hook`) and, for the
|
|
173
|
+
dialog, an `?estimate=<name>` canned `PriceEstimateControl` fixture (`tests/harness/estimate-fixtures.ts`
|
|
174
|
+
— no server, no Stripe). The harness pre-bundles every runtime dependency in `optimizeDeps.include`;
|
|
175
|
+
one discovered mid-navigation loads a second React and the page throws "Invalid hook call".
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
3
|
+
export declare const Select: (props: React.ComponentProps<typeof SelectPrimitive.Root>) => React.JSX.Element;
|
|
4
|
+
export declare const SelectValue: (props: React.ComponentProps<typeof SelectPrimitive.Value>) => React.JSX.Element;
|
|
5
|
+
export declare const SelectTrigger: ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger>) => React.JSX.Element;
|
|
6
|
+
export declare const SelectContent: ({ className, children, position, ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) => React.JSX.Element;
|
|
7
|
+
export declare const SelectItem: ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) => React.JSX.Element;
|
|
8
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../../src/@/components/ui/select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAGzD,eAAO,MAAM,MAAM,UAAW,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,sBAAwC,CAAA;AACvH,eAAO,MAAM,WAAW,UAAW,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,sBAAyC,CAAA;AAE9H,eAAO,MAAM,aAAa,sCAAuC,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,sBAepH,CAAA;AAED,eAAO,MAAM,aAAa,gDAA4D,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,sBAsBzI,CAAA;AAED,eAAO,MAAM,UAAU,sCAAuC,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,sBAa9G,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
export const Select = (props) => _jsx(SelectPrimitive.Root, { ...props });
|
|
5
|
+
export const SelectValue = (props) => _jsx(SelectPrimitive.Value, { ...props });
|
|
6
|
+
export const SelectTrigger = ({ className, children, ...props }) => (_jsxs(SelectPrimitive.Trigger, { className: cn('border-input data-[placeholder]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50', 'flex h-9 w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs', 'outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx("span", { "aria-hidden": "true", className: "text-muted-foreground shrink-0", children: "\u25BE" }) })] }));
|
|
7
|
+
export const SelectContent = ({ className, children, position = 'popper', ...props }) => (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { position: position, className: cn('bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-y-auto rounded-md border shadow-md', position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1', className), ...props, children: [_jsx(SelectPrimitive.ScrollUpButton, { className: "flex items-center justify-center py-1", children: _jsx("span", { "aria-hidden": "true", children: "\u25B4" }) }), _jsx(SelectPrimitive.Viewport, { className: cn('p-1', position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]'), children: children }), _jsx(SelectPrimitive.ScrollDownButton, { className: "flex items-center justify-center py-1", children: _jsx("span", { "aria-hidden": "true", children: "\u25BE" }) })] }) }));
|
|
8
|
+
export const SelectItem = ({ className, children, ...props }) => (_jsxs(SelectPrimitive.Item, { className: cn('focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50', className), ...props, children: [_jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { "aria-hidden": "true", children: "\u2713" }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
|
|
9
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../../src/@/components/ui/select.tsx"],"names":[],"mappings":";AACA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAwD,EAAE,EAAE,CAAC,KAAC,eAAe,CAAC,IAAI,OAAK,KAAK,GAAI,CAAA;AACvH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAyD,EAAE,EAAE,CAAC,KAAC,eAAe,CAAC,KAAK,OAAK,KAAK,GAAI,CAAA;AAE9H,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAwD,EAAE,EAAE,CAAC,CACxH,MAAC,eAAe,CAAC,OAAO,IACtB,SAAS,EAAE,EAAE,CACX,4GAA4G,EAC5G,iHAAiH,EACjH,qHAAqH,EACrH,SAAS,CACV,KACG,KAAK,aAER,QAAQ,EACT,KAAC,eAAe,CAAC,IAAI,IAAC,OAAO,kBAC3B,8BAAkB,MAAM,EAAC,SAAS,EAAC,gCAAgC,uBAAS,GACvD,IACC,CAC3B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAwD,EAAE,EAAE,CAAC,CAC7I,KAAC,eAAe,CAAC,MAAM,cACrB,MAAC,eAAe,CAAC,OAAO,IACtB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,EAAE,CACX,oHAAoH,EACpH,QAAQ,KAAK,QAAQ,IAAI,iEAAiE,EAC1F,SAAS,CACV,KACG,KAAK,aAET,KAAC,eAAe,CAAC,cAAc,IAAC,SAAS,EAAC,uCAAuC,YAC/E,8BAAkB,MAAM,uBAAS,GACF,EACjC,KAAC,eAAe,CAAC,QAAQ,IAAC,SAAS,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,QAAQ,IAAI,kDAAkD,CAAC,YACxH,QAAQ,GACgB,EAC3B,KAAC,eAAe,CAAC,gBAAgB,IAAC,SAAS,EAAC,uCAAuC,YACjF,8BAAkB,MAAM,uBAAS,GACA,IACX,GACH,CAC1B,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAqD,EAAE,EAAE,CAAC,CAClH,MAAC,eAAe,CAAC,IAAI,IACnB,SAAS,EAAE,EAAE,CACX,mOAAmO,EACnO,SAAS,CACV,KACG,KAAK,aAET,eAAM,SAAS,EAAC,4DAA4D,YAC1E,KAAC,eAAe,CAAC,aAAa,mBAAa,MAAM,uBAAkC,GAC9E,EACP,KAAC,eAAe,CAAC,QAAQ,cAAE,QAAQ,GAA4B,IAC1C,CACxB,CAAA"}
|
package/build/dialog.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { AmountCheckoutDialogProps } from './types.js';
|
|
2
|
-
export declare const AmountCheckoutDialog: ({ open, onOpenChange, policy, pending, onConfirm, }: AmountCheckoutDialogProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const AmountCheckoutDialog: ({ open, onOpenChange, policy, pending, onConfirm, estimate, }: AmountCheckoutDialogProps) => import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=dialog.d.ts.map
|
package/build/dialog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAM3D,eAAO,MAAM,oBAAoB,kEAE9B,yBAAyB,gCAmE3B,CAAA"}
|
package/build/dialog.js
CHANGED
|
@@ -7,10 +7,11 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
|
|
7
7
|
import { Input } from '@/components/ui/input';
|
|
8
8
|
import { Label } from '@/components/ui/label';
|
|
9
9
|
import { inputAmount, parseAmountMinor } from './amount.js';
|
|
10
|
+
import { PriceEstimateSummary } from './estimate-summary.js';
|
|
10
11
|
const money = (minor, currency, locale) => new Intl.NumberFormat(locale, {
|
|
11
12
|
style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2,
|
|
12
13
|
}).format(minor / 100);
|
|
13
|
-
export const AmountCheckoutDialog = ({ open, onOpenChange, policy, pending = false, onConfirm, }) => {
|
|
14
|
+
export const AmountCheckoutDialog = ({ open, onOpenChange, policy, pending = false, onConfirm, estimate, }) => {
|
|
14
15
|
const t = useI18nLib('web-payment', 'amount-checkout');
|
|
15
16
|
const [locale] = useLanguage();
|
|
16
17
|
const [value, setValue] = useState(() => inputAmount(policy.defaultMinor, locale));
|
|
@@ -38,6 +39,8 @@ export const AmountCheckoutDialog = ({ open, onOpenChange, policy, pending = fal
|
|
|
38
39
|
return _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { closeLabel: t('close'), children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('title') }), _jsx(DialogDescription, { children: t('description') })] }), _jsxs("div", { className: "grid gap-5", children: [_jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", "aria-label": t('presets'), children: policy.presetsMinor.map(preset => _jsx(Button, { type: "button", variant: preset === amountMinor ? 'default' : 'outline', disabled: pending, onClick: () => setValue(inputAmount(preset, locale)), children: money(preset, policy.currency, locale) }, preset)) }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { htmlFor: "payment-amount", children: t('custom') }), _jsx(Input, { id: "payment-amount", inputMode: "decimal", autoComplete: "off", value: value, disabled: pending, "aria-invalid": error != null, "aria-describedby": "payment-amount-help", onChange: event => setValue(event.target.value) }), _jsx("p", { id: "payment-amount-help", className: error == null ? 'text-muted-foreground text-xs' : 'text-destructive text-xs', children: error ?? t('bounds', {
|
|
39
40
|
minimum: money(policy.minimumMinor, policy.currency, locale),
|
|
40
41
|
maximum: money(policy.maximumMinor, policy.currency, locale),
|
|
41
|
-
}) })] }), _jsxs("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-2 rounded-lg border bg-muted/30 p-4 text-sm", children: [_jsx("dt", { className: "text-muted-foreground", children: t('credits') }), _jsx("dd", { children: valid && amountMinor != null ? money(amountMinor, policy.currency, locale) : '—' }), _jsx("dt", { className: "text-muted-foreground", children: t('adjustment') }), _jsx("dd", { children: valid ? money(adjustmentMinor, policy.currency, locale) : '—' }), _jsx("dt", { className: "font-medium", children: t('subtotal') }), _jsx("dd", { className: "font-medium", children: valid ? money(chargeMinor, policy.currency, locale) : '—' })] }),
|
|
42
|
+
}) })] }), _jsxs("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-2 rounded-lg border bg-muted/30 p-4 text-sm", children: [_jsx("dt", { className: "text-muted-foreground", children: t('credits') }), _jsx("dd", { children: valid && amountMinor != null ? money(amountMinor, policy.currency, locale) : '—' }), _jsx("dt", { className: "text-muted-foreground", children: t('adjustment') }), _jsx("dd", { children: valid ? money(adjustmentMinor, policy.currency, locale) : '—' }), _jsx("dt", { className: "font-medium", children: t('subtotal') }), _jsx("dd", { className: "font-medium", children: valid ? money(chargeMinor, policy.currency, locale) : '—' })] }), estimate != null
|
|
43
|
+
? valid && _jsx(PriceEstimateSummary, { control: estimate, subtotalMinor: chargeMinor, currency: policy.currency })
|
|
44
|
+
: _jsx("p", { className: "text-muted-foreground text-xs", children: t('tax-note') })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: pending, onClick: () => onOpenChange(false), children: t('cancel') }), _jsx(Button, { type: "button", disabled: !valid || pending, onClick: () => void submit(), children: pending ? t('pending') : t('continue') })] })] }) });
|
|
42
45
|
};
|
|
43
46
|
//# sourceMappingURL=dialog.js.map
|
package/build/dialog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/C,OAAO,EACL,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,GAClF,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../src/dialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small keyed TTL cache with in-flight de-duplication — the client-side twin of
|
|
3
|
+
* `@owlmeans/server-payment`'s `EstimateCache`, so opening the credit dialog twice in a row (or two
|
|
4
|
+
* mounts asking for the same plan/country) never issues a second request while the first is still
|
|
5
|
+
* on the wire, and a settled answer is reused for its TTL. A rejected fetch is never cached: the
|
|
6
|
+
* next call simply tries again.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EstimateCache<T> {
|
|
9
|
+
private readonly store;
|
|
10
|
+
private readonly inflight;
|
|
11
|
+
get(key: string): T | undefined;
|
|
12
|
+
load(key: string, ttlMs: number, factory: () => Promise<T>): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=estimate-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate-cache.d.ts","sourceRoot":"","sources":["../src/estimate-cache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,aAAa,CAAC,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqD;IAC3E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAM9B;IAEK,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmB5E;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small keyed TTL cache with in-flight de-duplication — the client-side twin of
|
|
3
|
+
* `@owlmeans/server-payment`'s `EstimateCache`, so opening the credit dialog twice in a row (or two
|
|
4
|
+
* mounts asking for the same plan/country) never issues a second request while the first is still
|
|
5
|
+
* on the wire, and a settled answer is reused for its TTL. A rejected fetch is never cached: the
|
|
6
|
+
* next call simply tries again.
|
|
7
|
+
*/
|
|
8
|
+
export class EstimateCache {
|
|
9
|
+
store = new Map();
|
|
10
|
+
inflight = new Map();
|
|
11
|
+
get(key) {
|
|
12
|
+
const hit = this.store.get(key);
|
|
13
|
+
if (hit == null || hit.expiresAt <= Date.now()) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return hit.value;
|
|
17
|
+
}
|
|
18
|
+
async load(key, ttlMs, factory) {
|
|
19
|
+
const hit = this.get(key);
|
|
20
|
+
if (hit !== undefined) {
|
|
21
|
+
return hit;
|
|
22
|
+
}
|
|
23
|
+
const pending = this.inflight.get(key);
|
|
24
|
+
if (pending != null) {
|
|
25
|
+
return pending;
|
|
26
|
+
}
|
|
27
|
+
const promise = factory().then(value => {
|
|
28
|
+
this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
29
|
+
this.inflight.delete(key);
|
|
30
|
+
return value;
|
|
31
|
+
}).catch(error => {
|
|
32
|
+
this.inflight.delete(key);
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
this.inflight.set(key, promise);
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=estimate-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate-cache.js","sourceRoot":"","sources":["../src/estimate-cache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IACP,KAAK,GAAG,IAAI,GAAG,EAA2C,CAAA;IAC1D,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAA;IAEzD,GAAG,CAAC,GAAW;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC/C,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,OAAO,GAAG,CAAC,KAAK,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,KAAa,EAAE,OAAyB;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,GAAG,CAAA;QACZ,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACtC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;YACrC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;YAC7D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACzB,OAAO,KAAK,CAAA;QACd,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;YACf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACzB,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC/B,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { PriceEstimateControl } from './types.js';
|
|
2
|
+
export interface CountrySelectProps {
|
|
3
|
+
/** ISO 3166-1 alpha-2, or `''` for the placeholder. */
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (country: string) => void;
|
|
6
|
+
/** Defaults to the `estimate.country` string (also the field's own label). */
|
|
7
|
+
label?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The billing-country picker alone — the piece `PriceEstimateSummary` composes for a single,
|
|
13
|
+
* self-contained estimate, and what a shared picker (several estimates, one country — a plan
|
|
14
|
+
* comparison table) renders once and drives every `usePriceEstimate({ country, onCountryChange })`
|
|
15
|
+
* with. Country names are the viewer's own language, alphabetically ordered.
|
|
16
|
+
*/
|
|
17
|
+
export declare const CountrySelect: ({ value, onChange, label, id, className }: CountrySelectProps) => import("react").JSX.Element;
|
|
18
|
+
export interface PriceEstimateAmountProps {
|
|
19
|
+
control: Pick<PriceEstimateControl, 'estimate' | 'loading' | 'failed'>;
|
|
20
|
+
/** The amount to show tax and a total for — may differ from the estimate's own reference amount. */
|
|
21
|
+
subtotalMinor: number;
|
|
22
|
+
currency: string;
|
|
23
|
+
/** Appended after the total row, e.g. `"/ month"`. */
|
|
24
|
+
suffix?: string;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What Stripe Tax says at the estimate's country, with NO country picker of its own: the rate(s),
|
|
29
|
+
* an estimated total, and one sentence for whichever status leaves no number to trust (reverse
|
|
30
|
+
* charge, no tax, "at checkout", "choose a country"). Several of these may share one
|
|
31
|
+
* `CountrySelect` (a plan comparison table).
|
|
32
|
+
*
|
|
33
|
+
* A country whose currency differs from `currency` (Adaptive Pricing found a rate) shows the tax
|
|
34
|
+
* and the total in THAT currency instead, marked `≈` throughout — never both currencies at once,
|
|
35
|
+
* which would read as two different prices for the same line. The rate percentage itself is
|
|
36
|
+
* currency-agnostic and always shows as Stripe stated it.
|
|
37
|
+
*
|
|
38
|
+
* `data-price-estimate` / `data-status` are stable test hooks.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PriceEstimateAmount: ({ control, subtotalMinor, currency, suffix, className }: PriceEstimateAmountProps) => import("react").JSX.Element;
|
|
41
|
+
export interface PriceEstimateSummaryProps {
|
|
42
|
+
control: PriceEstimateControl;
|
|
43
|
+
/** The amount the summary shows tax and a total for — may differ from the estimate's own reference amount. */
|
|
44
|
+
subtotalMinor: number;
|
|
45
|
+
currency: string;
|
|
46
|
+
/** Appended after the total row, e.g. `"/ month"`. */
|
|
47
|
+
suffix?: string;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A self-contained estimate: `CountrySelect` bound to the control's own `country`/`onCountryChange`,
|
|
52
|
+
* plus `PriceEstimateAmount` beneath it. What a single estimate (the credit dialog) wants; several
|
|
53
|
+
* estimates sharing one picker (a plan comparison table) compose the two pieces separately instead.
|
|
54
|
+
*/
|
|
55
|
+
export declare const PriceEstimateSummary: ({ control, subtotalMinor, currency, suffix, className }: PriceEstimateSummaryProps) => import("react").JSX.Element;
|
|
56
|
+
//# sourceMappingURL=estimate-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate-summary.d.ts","sourceRoot":"","sources":["../src/estimate-summary.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAwBtD,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,8CAA0E,kBAAkB,gCAmBrH,CAAA;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAA;IACtE,oGAAoG;IACpG,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,4DAA6D,wBAAwB,gCA+CpH,CAAA;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,oBAAoB,CAAA;IAC7B,8GAA8G;IAC9G,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4DAA6D,yBAAyB,gCAOtH,CAAA"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useMemo } from 'react';
|
|
3
|
+
import { useI18nLib, useLanguage } from '@owlmeans/client-i18n';
|
|
4
|
+
import { COUNTRY_CODES, TaxEstimateStatus, estimateOf } from '@owlmeans/payment';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { Label } from '@/components/ui/label';
|
|
7
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
8
|
+
const money = (minor, currency, locale) => new Intl.NumberFormat(locale, {
|
|
9
|
+
style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2,
|
|
10
|
+
}).format(minor / 100);
|
|
11
|
+
const localMoney = (amount, currency, locale) => {
|
|
12
|
+
try {
|
|
13
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase() }).format(amount);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return `${amount.toFixed(2)} ${currency.toUpperCase()}`;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** The country names a `<Select>` offers, in the viewer's own language and alphabetical order. */
|
|
20
|
+
const useCountryOptions = (locale) => useMemo(() => {
|
|
21
|
+
let names = null;
|
|
22
|
+
try {
|
|
23
|
+
names = new Intl.DisplayNames([locale], { type: 'region' });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
names = null;
|
|
27
|
+
}
|
|
28
|
+
const collator = new Intl.Collator(locale);
|
|
29
|
+
return COUNTRY_CODES
|
|
30
|
+
.map(code => ({ code, name: names?.of(code) ?? code }))
|
|
31
|
+
.sort((a, b) => collator.compare(a.name, b.name));
|
|
32
|
+
}, [locale]);
|
|
33
|
+
/**
|
|
34
|
+
* The billing-country picker alone — the piece `PriceEstimateSummary` composes for a single,
|
|
35
|
+
* self-contained estimate, and what a shared picker (several estimates, one country — a plan
|
|
36
|
+
* comparison table) renders once and drives every `usePriceEstimate({ country, onCountryChange })`
|
|
37
|
+
* with. Country names are the viewer's own language, alphabetically ordered.
|
|
38
|
+
*/
|
|
39
|
+
export const CountrySelect = ({ value, onChange, label, id = 'price-estimate-country', className }) => {
|
|
40
|
+
const t = useI18nLib('web-payment', 'estimate');
|
|
41
|
+
const [locale] = useLanguage();
|
|
42
|
+
const countries = useCountryOptions(locale);
|
|
43
|
+
const text = label ?? t('country');
|
|
44
|
+
return _jsxs("div", { className: cn('grid gap-2', className), children: [_jsx(Label, { htmlFor: id, children: text }), _jsxs(Select, { value: value, onValueChange: onChange, children: [_jsx(SelectTrigger, { id: id, "aria-label": text, children: _jsx(SelectValue, { placeholder: t('country-placeholder') }) }), _jsx(SelectContent, { children: countries.map(({ code, name }) => _jsx(SelectItem, { value: code, children: name }, code)) })] })] });
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* What Stripe Tax says at the estimate's country, with NO country picker of its own: the rate(s),
|
|
48
|
+
* an estimated total, and one sentence for whichever status leaves no number to trust (reverse
|
|
49
|
+
* charge, no tax, "at checkout", "choose a country"). Several of these may share one
|
|
50
|
+
* `CountrySelect` (a plan comparison table).
|
|
51
|
+
*
|
|
52
|
+
* A country whose currency differs from `currency` (Adaptive Pricing found a rate) shows the tax
|
|
53
|
+
* and the total in THAT currency instead, marked `≈` throughout — never both currencies at once,
|
|
54
|
+
* which would read as two different prices for the same line. The rate percentage itself is
|
|
55
|
+
* currency-agnostic and always shows as Stripe stated it.
|
|
56
|
+
*
|
|
57
|
+
* `data-price-estimate` / `data-status` are stable test hooks.
|
|
58
|
+
*/
|
|
59
|
+
export const PriceEstimateAmount = ({ control, subtotalMinor, currency, suffix, className }) => {
|
|
60
|
+
const t = useI18nLib('web-payment', 'estimate');
|
|
61
|
+
const [locale] = useLanguage();
|
|
62
|
+
const { estimate, loading, failed } = control;
|
|
63
|
+
const derived = estimate != null ? estimateOf(subtotalMinor, estimate) : null;
|
|
64
|
+
const status = estimate?.tax.status;
|
|
65
|
+
const numeric = status === TaxEstimateStatus.Taxed || status === TaxEstimateStatus.ReverseCharge
|
|
66
|
+
|| status === TaxEstimateStatus.None;
|
|
67
|
+
const rateLabel = estimate != null && estimate.tax.rates.length > 0
|
|
68
|
+
? estimate.tax.rates
|
|
69
|
+
.map(rate => t('rate', { type: t(`tax-type.${rate.type}`), percentage: rate.percentage }))
|
|
70
|
+
.join(' + ')
|
|
71
|
+
: null;
|
|
72
|
+
const local = derived?.local;
|
|
73
|
+
const taxText = local != null
|
|
74
|
+
? (derived?.taxMinor != null ? localMoney(local.taxAmount, local.currency, locale) : null)
|
|
75
|
+
: (derived?.taxMinor != null ? money(derived.taxMinor, currency, locale) : null);
|
|
76
|
+
const totalText = local != null
|
|
77
|
+
? (derived?.totalMinor != null ? localMoney(local.totalAmount, local.currency, locale) : null)
|
|
78
|
+
: (derived?.totalMinor != null ? money(derived.totalMinor, currency, locale) : null);
|
|
79
|
+
const approx = local != null ? '≈ ' : '';
|
|
80
|
+
return _jsxs("div", { "data-price-estimate": "", "data-status": status ?? 'pending', className: cn('grid gap-1.5', className), children: [estimate == null && loading && _jsx("p", { className: "text-muted-foreground text-xs", children: t('loading') }), estimate == null && !loading && failed && _jsx("p", { className: "text-destructive text-xs", children: t('failed') }), estimate != null && _jsxs(_Fragment, { children: [numeric && taxText != null && totalText != null && _jsxs("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-1 text-sm", "data-estimate-rows": "", children: [status === TaxEstimateStatus.Taxed && rateLabel != null && _jsxs(Fragment, { children: [_jsx("dt", { className: "text-muted-foreground", "data-estimate-rate": "", children: rateLabel }), _jsxs("dd", { "data-estimate-tax": "", children: [approx, taxText] })] }), _jsx("dt", { className: "font-medium", children: t('total') }), _jsxs("dd", { className: "font-medium", "data-estimate-total": "", children: [approx, totalText, suffix != null ? ` ${suffix}` : ''] })] }), numeric && local != null && _jsx("p", { className: "text-muted-foreground text-xs", "data-estimate-converted": "", children: t('converted-note') }), _jsx("p", { className: "text-muted-foreground text-xs", "data-estimate-status": "", children: t(`status.${status}`) })] })] });
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A self-contained estimate: `CountrySelect` bound to the control's own `country`/`onCountryChange`,
|
|
84
|
+
* plus `PriceEstimateAmount` beneath it. What a single estimate (the credit dialog) wants; several
|
|
85
|
+
* estimates sharing one picker (a plan comparison table) compose the two pieces separately instead.
|
|
86
|
+
*/
|
|
87
|
+
export const PriceEstimateSummary = ({ control, subtotalMinor, currency, suffix, className }) => {
|
|
88
|
+
const { country, onCountryChange } = control;
|
|
89
|
+
return _jsxs("div", { className: cn('grid gap-3', className), children: [_jsx(CountrySelect, { value: country, onChange: onCountryChange }), _jsx(PriceEstimateAmount, { control: control, subtotalMinor: subtotalMinor, currency: currency, suffix: suffix })] });
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=estimate-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate-summary.js","sourceRoot":"","sources":["../src/estimate-summary.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RegisteredEntrypoint } from '@owlmeans/entrypoint';
|
|
2
|
+
import type { PriceEstimate } from '@owlmeans/payment';
|
|
3
|
+
import type { PriceEstimateArguments, PriceEstimateControl, PriceEstimateRequest } from './types.js';
|
|
4
|
+
export interface UsePriceEstimateOptions {
|
|
5
|
+
/** Fetch (and refetch on a country change) only while `true` — a closed dialog costs nothing. */
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
ttlMs?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Controls the country from outside (several estimates sharing one picker — a plan comparison
|
|
10
|
+
* table). Given: the hook never owns `useState` for it, never preselects from `source:
|
|
11
|
+
* 'customer'` (the owner does, once, for whichever estimate answers first), and the returned
|
|
12
|
+
* control's `onCountryChange` calls this back instead of an internal setter. Absent (the
|
|
13
|
+
* default): the hook is self-contained, exactly as a standalone dialog needs.
|
|
14
|
+
*/
|
|
15
|
+
country?: string;
|
|
16
|
+
onCountryChange?: (country: string) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A live `PriceEstimate` for one protocol/request, with a country the caller may change.
|
|
20
|
+
*
|
|
21
|
+
* Fetches once `enabled` and again on every `onCountryChange`, from a client-side cache keyed by
|
|
22
|
+
* the entry's alias, the request body and the country — a TTL match answers instantly, an identical
|
|
23
|
+
* in-flight request is shared. Uncontrolled (no `opts.country`): the first answer whose `source` is
|
|
24
|
+
* `'customer'` preselects its `country`, once, so a returning buyer sees their own country without
|
|
25
|
+
* picking it. Controlled (`opts.country` given): the country and its preselection are the caller's.
|
|
26
|
+
*/
|
|
27
|
+
export declare const usePriceEstimate: <Request extends PriceEstimateRequest>(entry: RegisteredEntrypoint<Request, PriceEstimate>, opts: UsePriceEstimateOptions, ...request: PriceEstimateArguments<Request>) => PriceEstimateControl;
|
|
28
|
+
//# sourceMappingURL=estimate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate.d.ts","sourceRoot":"","sources":["../src/estimate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAOpG,MAAM,WAAW,uBAAuB;IACtC,iGAAiG;IACjG,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,oBAAoB,SAC5D,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,QAAQ,uBAAuB,cACtE,sBAAsB,CAAC,OAAO,CAAC,KAC1C,oBAsDF,CAAA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { EstimateCache } from './estimate-cache.js';
|
|
3
|
+
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
4
|
+
/** Shared across every mounted dialog/estimate card in the tab — the server already caches per TTL too. */
|
|
5
|
+
const cache = new EstimateCache();
|
|
6
|
+
/**
|
|
7
|
+
* A live `PriceEstimate` for one protocol/request, with a country the caller may change.
|
|
8
|
+
*
|
|
9
|
+
* Fetches once `enabled` and again on every `onCountryChange`, from a client-side cache keyed by
|
|
10
|
+
* the entry's alias, the request body and the country — a TTL match answers instantly, an identical
|
|
11
|
+
* in-flight request is shared. Uncontrolled (no `opts.country`): the first answer whose `source` is
|
|
12
|
+
* `'customer'` preselects its `country`, once, so a returning buyer sees their own country without
|
|
13
|
+
* picking it. Controlled (`opts.country` given): the country and its preselection are the caller's.
|
|
14
|
+
*/
|
|
15
|
+
export const usePriceEstimate = (entry, opts, ...request) => {
|
|
16
|
+
const entryRef = useRef(entry);
|
|
17
|
+
entryRef.current = entry;
|
|
18
|
+
const base = (request[0] ?? {});
|
|
19
|
+
const bodyKey = JSON.stringify(base.body ?? {});
|
|
20
|
+
const controlled = opts.country !== undefined;
|
|
21
|
+
const [internalCountry, setInternalCountry] = useState('');
|
|
22
|
+
const country = controlled ? opts.country : internalCountry;
|
|
23
|
+
const [estimate, setEstimate] = useState(null);
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const [failed, setFailed] = useState(false);
|
|
26
|
+
const preselectedRef = useRef(false);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!opts.enabled) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let active = true;
|
|
32
|
+
const key = `${entryRef.current.alias}:${bodyKey}:${country}`;
|
|
33
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setFailed(false);
|
|
36
|
+
cache.load(key, ttlMs, async () => await entryRef.current.call({
|
|
37
|
+
...base, body: { ...(base.body ?? {}), ...(country !== '' ? { country } : {}) },
|
|
38
|
+
})).then(result => {
|
|
39
|
+
if (!active)
|
|
40
|
+
return;
|
|
41
|
+
setEstimate(result);
|
|
42
|
+
setLoading(false);
|
|
43
|
+
if (!controlled && !preselectedRef.current && country === '' && result.source === 'customer' && result.country != null) {
|
|
44
|
+
preselectedRef.current = true;
|
|
45
|
+
setInternalCountry(result.country);
|
|
46
|
+
}
|
|
47
|
+
}).catch(() => {
|
|
48
|
+
if (active) {
|
|
49
|
+
setFailed(true);
|
|
50
|
+
setLoading(false);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return () => { active = false; };
|
|
54
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
+
}, [opts.enabled, opts.ttlMs, bodyKey, country]);
|
|
56
|
+
const onCountryChange = useCallback((next) => {
|
|
57
|
+
if (controlled) {
|
|
58
|
+
opts.onCountryChange?.(next);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
preselectedRef.current = true;
|
|
62
|
+
setInternalCountry(next);
|
|
63
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
64
|
+
}, [controlled, opts.onCountryChange]);
|
|
65
|
+
return { estimate, country, loading, failed, onCountryChange };
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=estimate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate.js","sourceRoot":"","sources":["../src/estimate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGhE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnD,MAAM,cAAc,GAAG,CAAC,GAAG,MAAM,CAAA;AAEjC,2GAA2G;AAC3G,MAAM,KAAK,GAAG,IAAI,aAAa,EAAiB,CAAA;AAiBhD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,KAAmD,EAAE,IAA6B,EAClF,GAAG,OAAwC,EACrB,EAAE;IACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;IACxB,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAA0E,CAAA;IACxG,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,KAAK,SAAS,CAAA;IAE7C,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC1D,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAQ,CAAC,CAAC,CAAC,eAAe,CAAA;IAC5D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAuB,IAAI,CAAC,CAAA;IACpE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAM;QACR,CAAC;QACD,IAAI,MAAM,GAAG,IAAI,CAAA;QACjB,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,IAAI,OAAO,EAAE,CAAA;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,cAAc,CAAA;QAC1C,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7D,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACvE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;YACzB,IAAI,CAAC,MAAM;gBAAE,OAAM;YACnB,WAAW,CAAC,MAAM,CAAC,CAAA;YACnB,UAAU,CAAC,KAAK,CAAC,CAAA;YACjB,IAAI,CAAC,UAAU,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,OAAO,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;gBACvH,cAAc,CAAC,OAAO,GAAG,IAAI,CAAA;gBAC7B,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACpC,CAAC;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,IAAI,MAAM,EAAE,CAAC;gBACX,SAAS,CAAC,IAAI,CAAC,CAAA;gBACf,UAAU,CAAC,KAAK,CAAC,CAAA;YACnB,CAAC;QACH,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,CAAA,CAAC,CAAC,CAAA;QAC/B,uDAAuD;IACzD,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAEhD,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE;QACnD,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAA;YAC5B,OAAM;QACR,CAAC;QACD,cAAc,CAAC,OAAO,GAAG,IAAI,CAAA;QAC7B,kBAAkB,CAAC,IAAI,CAAC,CAAA;QACxB,uDAAuD;IACzD,CAAC,EAAE,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,CAAA;IAEtC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;AAChE,CAAC,CAAA"}
|
package/build/i18n/be.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "amount-checkout": { "title": "Папоўніць крэдыты", "description": "Выберыце чысты кошт крэдытаў для арганізацыі.", "presets": "Гатовыя сумы", "custom": "Свая сума", "invalid": "Увядзіце карэктную суму, не больш за два знакі пасля коскі.", "below": "Мінімальная сума — {{amount}}.", "above": "Максімальная сума — {{amount}}.", "bounds": "Выберыце суму ад {{minimum}} да {{maximum}}.", "credits": "Кошт крэдытаў", "adjustment": "Карэкціроўка апрацоўкі", "subtotal": "Сума да падатку", "tax-note": "Stripe разлічыць падатак падчас аплаты.", "cancel": "Скасаваць", "continue": "Перайсці ў Stripe", "pending": "Адкрываем аплату…", "close": "Закрыць" }, "entitlement": { "plan": { "current": "Бягучы план" }, "status": { "trial": "Пробны перыяд", "trial-on": "Пробны перыяд да {{date}}", "past-due": "Плацёж пратэрмінаваны", "cancel-scheduled": "Адмена запланавана", "cancel-scheduled-on": "Адмяняецца {{date}}", "paused": "На паўзе", "paused-on": "На паўзе з {{date}}", "suspended": "Прыпынены", "free": "Бясплатны план", "renews-on": "Падаўжаецца {{date}}", "active": "Актыўны", "created": "Чакае актывацыі", "canceled": "Адменены", "expired": "Тэрмін скончыўся", "ended": "Завершаны", "blocked": "Заблакаваны" }, "limit": { "usage": "{{used}} з {{limit}}", "usage-unit": "{{used}} з {{limit}} {{unit}}", "not-included": "Не ўваходзіць у план", "exhausted": "Ліміт вычарпаны", "resets-on": "Абновіцца {{date}}" }, "promo": { "free-until": "Бясплатна да {{date}}", "grandfathered": "Захавана для вашага плана", "ended": "Акцыя завершана" }, "capability": { "included": "Уваходзіць у план", "not-included": "Не ўваходзіць у план" } } }
|
|
1
|
+
{ "amount-checkout": { "title": "Папоўніць крэдыты", "description": "Выберыце чысты кошт крэдытаў для арганізацыі.", "presets": "Гатовыя сумы", "custom": "Свая сума", "invalid": "Увядзіце карэктную суму, не больш за два знакі пасля коскі.", "below": "Мінімальная сума — {{amount}}.", "above": "Максімальная сума — {{amount}}.", "bounds": "Выберыце суму ад {{minimum}} да {{maximum}}.", "credits": "Кошт крэдытаў", "adjustment": "Карэкціроўка апрацоўкі", "subtotal": "Сума да падатку", "tax-note": "Stripe разлічыць падатак падчас аплаты.", "cancel": "Скасаваць", "continue": "Перайсці ў Stripe", "pending": "Адкрываем аплату…", "close": "Закрыць" }, "entitlement": { "plan": { "current": "Бягучы план" }, "status": { "trial": "Пробны перыяд", "trial-on": "Пробны перыяд да {{date}}", "past-due": "Плацёж пратэрмінаваны", "cancel-scheduled": "Адмена запланавана", "cancel-scheduled-on": "Адмяняецца {{date}}", "paused": "На паўзе", "paused-on": "На паўзе з {{date}}", "suspended": "Прыпынены", "free": "Бясплатны план", "renews-on": "Падаўжаецца {{date}}", "active": "Актыўны", "created": "Чакае актывацыі", "canceled": "Адменены", "expired": "Тэрмін скончыўся", "ended": "Завершаны", "blocked": "Заблакаваны" }, "limit": { "usage": "{{used}} з {{limit}}", "usage-unit": "{{used}} з {{limit}} {{unit}}", "not-included": "Не ўваходзіць у план", "exhausted": "Ліміт вычарпаны", "resets-on": "Абновіцца {{date}}" }, "promo": { "free-until": "Бясплатна да {{date}}", "grandfathered": "Захавана для вашага плана", "ended": "Акцыя завершана" }, "capability": { "included": "Уваходзіць у план", "not-included": "Не ўваходзіць у план" } }, "estimate": { "country": "Краіна плацельшчыка", "country-placeholder": "Выберыце краіну", "tax-type": { "vat": "ПДВ", "gst": "GST", "sales-tax": "Падатак з продажаў", "tax": "Падатак" }, "rate": "{{type}} ({{percentage}}%)", "total": "Прыблізная сума", "converted-note": "Пералічана па бягучым курсе Stripe; дакладная сума і магчымая камісія за канвертацыю будуць пацверджаны падчас аплаты.", "status": { "taxed": "Падатак ужо ўключаны ў прыблізную суму ніжэй.", "reverse-charge": "Адваротнае налічэнне — пры ўказаным нумары ПДВ вы ўлічваеце падатак самастойна, таму мы нічога не дадаём.", "none": "Для гэтай краіны падатак не прымяняецца.", "at-checkout": "Дакладны падатак будзе разлічаны падчас аплаты.", "location-required": "Выберыце краіну плацельшчыка, каб убачыць разлік." }, "loading": "Правяраем падатак…", "failed": "Зараз не ўдалося праверыць падатак." } }
|