@owlmeans/web-payment 0.1.18-rc.20 → 0.1.18-rc.22
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 +171 -31
- package/build/@/components/ui/checkbox.d.ts +5 -0
- package/build/@/components/ui/checkbox.d.ts.map +1 -0
- package/build/@/components/ui/checkbox.js +6 -0
- package/build/@/components/ui/checkbox.js.map +1 -0
- package/build/checkout-limit.d.ts +11 -0
- package/build/checkout-limit.d.ts.map +1 -0
- package/build/checkout-limit.js +34 -0
- package/build/checkout-limit.js.map +1 -0
- package/build/consumer/cancellation.d.ts +20 -0
- package/build/consumer/cancellation.d.ts.map +1 -0
- package/build/consumer/cancellation.js +115 -0
- package/build/consumer/cancellation.js.map +1 -0
- package/build/consumer/copy.d.ts +23 -0
- package/build/consumer/copy.d.ts.map +1 -0
- package/build/consumer/copy.js +75 -0
- package/build/consumer/copy.js.map +1 -0
- package/build/consumer/ensure.d.ts +56 -0
- package/build/consumer/ensure.d.ts.map +1 -0
- package/build/consumer/ensure.js +71 -0
- package/build/consumer/ensure.js.map +1 -0
- package/build/consumer/fields.d.ts +43 -0
- package/build/consumer/fields.d.ts.map +1 -0
- package/build/consumer/fields.js +19 -0
- package/build/consumer/fields.js.map +1 -0
- package/build/consumer/hooks.d.ts +113 -0
- package/build/consumer/hooks.d.ts.map +1 -0
- package/build/consumer/hooks.js +225 -0
- package/build/consumer/hooks.js.map +1 -0
- package/build/consumer/index.d.ts +14 -0
- package/build/consumer/index.d.ts.map +1 -0
- package/build/consumer/index.js +12 -0
- package/build/consumer/index.js.map +1 -0
- package/build/consumer/legal.d.ts +53 -0
- package/build/consumer/legal.d.ts.map +1 -0
- package/build/consumer/legal.js +77 -0
- package/build/consumer/legal.js.map +1 -0
- package/build/consumer/performance-consent-dialog.d.ts +18 -0
- package/build/consumer/performance-consent-dialog.d.ts.map +1 -0
- package/build/consumer/performance-consent-dialog.js +71 -0
- package/build/consumer/performance-consent-dialog.js.map +1 -0
- package/build/consumer/provider.d.ts +33 -0
- package/build/consumer/provider.d.ts.map +1 -0
- package/build/consumer/provider.js +26 -0
- package/build/consumer/provider.js.map +1 -0
- package/build/consumer/refusal.d.ts +35 -0
- package/build/consumer/refusal.d.ts.map +1 -0
- package/build/consumer/refusal.js +98 -0
- package/build/consumer/refusal.js.map +1 -0
- package/build/consumer/subscription-start-dialog.d.ts +16 -0
- package/build/consumer/subscription-start-dialog.d.ts.map +1 -0
- package/build/consumer/subscription-start-dialog.js +43 -0
- package/build/consumer/subscription-start-dialog.js.map +1 -0
- package/build/consumer/types.d.ts +114 -0
- package/build/consumer/types.d.ts.map +1 -0
- package/build/consumer/types.js +2 -0
- package/build/consumer/types.js.map +1 -0
- package/build/consumer/withdrawal.d.ts +29 -0
- package/build/consumer/withdrawal.d.ts.map +1 -0
- package/build/consumer/withdrawal.js +140 -0
- package/build/consumer/withdrawal.js.map +1 -0
- package/build/dialog.d.ts +1 -1
- package/build/dialog.d.ts.map +1 -1
- package/build/dialog.js +42 -16
- package/build/dialog.js.map +1 -1
- package/build/estimate-summary.d.ts +9 -1
- package/build/estimate-summary.d.ts.map +1 -1
- package/build/estimate-summary.js +10 -7
- package/build/estimate-summary.js.map +1 -1
- package/build/estimate.d.ts +6 -0
- package/build/estimate.d.ts.map +1 -1
- package/build/estimate.js +26 -1
- package/build/estimate.js.map +1 -1
- package/build/format.d.ts +15 -0
- package/build/format.d.ts.map +1 -0
- package/build/format.js +68 -0
- package/build/format.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/fr.json +110 -15
- package/build/i18n/pl.json +1 -1
- package/build/i18n/ru.json +1 -1
- package/build/i18n/uk.json +1 -1
- package/build/i18n.d.ts +2 -1
- package/build/i18n.d.ts.map +1 -1
- package/build/i18n.js +10 -8
- package/build/i18n.js.map +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 +22 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +18 -7
- package/src/@/components/ui/checkbox.tsx +23 -0
- package/src/checkout-limit.tsx +48 -0
- package/src/consumer/cancellation.tsx +214 -0
- package/src/consumer/copy.ts +93 -0
- package/src/consumer/ensure.ts +124 -0
- package/src/consumer/fields.tsx +89 -0
- package/src/consumer/hooks.ts +358 -0
- package/src/consumer/index.ts +13 -0
- package/src/consumer/legal.tsx +129 -0
- package/src/consumer/performance-consent-dialog.tsx +132 -0
- package/src/consumer/provider.tsx +49 -0
- package/src/consumer/refusal.ts +110 -0
- package/src/consumer/subscription-start-dialog.tsx +87 -0
- package/src/consumer/types.ts +121 -0
- package/src/consumer/withdrawal.tsx +279 -0
- package/src/dialog.tsx +53 -23
- package/src/estimate-summary.tsx +21 -11
- package/src/estimate.ts +27 -1
- package/src/format.ts +71 -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/fr.json +110 -15
- package/src/i18n/pl.json +1 -1
- package/src/i18n/ru.json +1 -1
- package/src/i18n/uk.json +1 -1
- package/src/i18n.ts +11 -8
- package/src/index.ts +2 -0
- package/src/types.ts +23 -1
- package/tests/consent-refusal.spec.ts +83 -0
- package/tests/consumer-hooks.spec.ts +205 -0
- package/tests/consumer.spec.ts +343 -0
- package/tests/context.ts +1 -1
- package/tests/harness/consumer-fixtures.ts +105 -0
- package/tests/harness/consumer.tsx +197 -0
- package/tests/harness/mount.tsx +9 -2
- package/tests/i18n.spec.ts +41 -5
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.39
|
|
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.22",
|
|
5
|
+
"generatedAt": "2026-09-24T14:00:35.062Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-payment
|
|
3
|
-
description: Public React payment UI for OwlMeans web apps — protocol-bound checkout/balance hooks, same-window redirects, entitlement helpers and the
|
|
3
|
+
description: Public React payment UI for OwlMeans web apps — protocol-bound checkout/balance hooks, same-window redirects, entitlement helpers, the themed AmountCheckoutDialog with its per-entity checkout limit, the locked-country price estimate, and the EU consumer-rights pieces of the ./consumer subpath (spend consent, subscription start, withdrawal and cancellation functions, the consent gate). Use when building payment UI with @owlmeans/web-payment.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
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.22`
|
|
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
|
|
14
14
|
presentational pieces over the entitlement view of `@owlmeans/payment` (the model itself is the
|
|
15
|
-
`entitlements` skill)
|
|
15
|
+
`entitlements` skill), and — on the `./consumer` subpath — the consumer-rights pieces over
|
|
16
|
+
`@owlmeans/payment`'s consumer-rights contracts and legal copy (the `payment` skill).
|
|
16
17
|
|
|
17
18
|
## Protocol-bound hooks
|
|
18
19
|
|
|
@@ -69,23 +70,25 @@ and action text come from the application). Every one accepts `className`.
|
|
|
69
70
|
| `PlanCard` | `plan: EntitlementPlanView`, `offer?: { sku, title, priceLabel, highlight? }`, `current?`, `pending?`, `actionLabel?`, `onAction?(sku)`, `children?` | Title; the status line (with a tone dot) for the entity's own plan, or for an offer only when it is current; a "Current plan" badge; `priceLabel`; children; one action button (outline when current, disabled while `pending`). `current` defaults to `offer.sku === plan.sku`. |
|
|
70
71
|
| `LimitMeter` | `limit: LimitView`, `label`, `showReset? = true`, `compact?` | `used of limit` (with `unit`), a `Progress` bar, "Limit reached" when exhausted, "Resets on …" for a window limit, the promo inscription. `limit: 0` reads "Not included" and draws no bar. |
|
|
71
72
|
| `CapabilityList` | `capabilities: CapabilityView[]`, `labels: Record<param, string>`, `onlyGranted?` | One row per labelled param in view order (duplicate params collapse, granted wins); unlabelled rows are not rendered; ungranted rows are muted; promo inscription under the label. |
|
|
73
|
+
| `CheckoutLimitNote` | `limit: CheckoutLimitView \| null`, `reasonLabel?: string \| (reason) => string` | Nothing for `null` or a limit that narrowed nothing; else "You can add up to {max} right now" (or "You cannot add credits right now" when `blocked`), the reason's sentence (`per-purchase`, `window`, `hold`, any other → generic; `reasonLabel` replaces it) and "More becomes available on {resetsAt}" (UTC, to the minute). |
|
|
72
74
|
|
|
73
75
|
- **Stable selectors** for tests and styling: `[data-plan-card]` with `data-plan-sku`,
|
|
74
76
|
`data-plan-status` (the line kind), `data-plan-tone`, `data-current`; `[data-plan-status-line]`,
|
|
75
77
|
`[data-plan-current]`, `[data-plan-price]`; `[data-limit-meter]` with `data-limit-key`,
|
|
76
78
|
`data-limit-kind`, `data-included`, `data-exhausted`, and `[data-limit-usage]`,
|
|
77
79
|
`[data-limit-resets]`, `[data-limit-exhausted]`; `[data-capability-list]`, `[data-capability]`
|
|
78
|
-
with `data-granted`; `[data-promo]` carrying the inscription kind
|
|
80
|
+
with `data-granted`; `[data-promo]` carrying the inscription kind; `[data-checkout-limit]` with
|
|
81
|
+
`data-max-minor`, `data-blocked` (`"true"`/`"false"`), `data-reason`, `data-resets-at` (ISO).
|
|
79
82
|
- **Dates render as calendar UTC days** (`dateStyle: 'medium'`) in the current language — the
|
|
80
83
|
calendar limit windows count in, so a reset date names the day the counter rolls.
|
|
81
84
|
- Promo copy: "Free until {{date}}", "Included for your plan", "Promotion ended".
|
|
82
85
|
|
|
83
86
|
## AmountCheckoutDialog
|
|
84
87
|
|
|
85
|
-
The dialog takes `{ open, onOpenChange, policy, pending, onConfirm, estimate? }`.
|
|
86
|
-
minor units, accepts locale decimal separators and at most two decimals, shows
|
|
87
|
-
plus custom input, and reports below/above-bound errors without clamping.
|
|
88
|
-
when invalid or pending.
|
|
88
|
+
The dialog takes `{ open, onOpenChange, policy, pending, onConfirm, estimate?, limit?, legalNote? }`.
|
|
89
|
+
It keeps integer minor units, accepts locale decimal separators and at most two decimals, shows
|
|
90
|
+
configured presets plus custom input, and reports below/above-bound errors without clamping.
|
|
91
|
+
Confirmation is disabled when invalid or pending.
|
|
89
92
|
|
|
90
93
|
It shows net credit value, processing adjustment and pre-tax subtotal from `chargeAmountMinor`.
|
|
91
94
|
With no `estimate`, the plain note that Stripe calculates applicable tax; with one, `PriceEstimateSummary`
|
|
@@ -93,6 +96,19 @@ renders instead (see below) against the SAME `chargeMinor`, so the country picke
|
|
|
93
96
|
track whatever the buyer has typed. The policy maximum bounds net credit, not the adjusted or
|
|
94
97
|
tax-inclusive debit.
|
|
95
98
|
|
|
99
|
+
- **`limit`** (`AmountPolicyView.limit`, what the entity may buy NOW) narrows `policy` through
|
|
100
|
+
`@owlmeans/payment`'s `narrowAmountPolicy` — the same computation the server refuses with
|
|
101
|
+
(`CheckoutLimitExceeded`), so the control and a refusal cannot disagree. Presets above the
|
|
102
|
+
maximum disappear, the default is clamped, the above-bound error names the narrowed maximum
|
|
103
|
+
("The most you can add right now is …"), and a `CheckoutLimitNote` shows above the presets.
|
|
104
|
+
`policy` may be the plan's or one already narrowed (`AmountPolicyView.policy`) — narrowing is
|
|
105
|
+
idempotent. **`blocked`** disables the presets, the input and the confirm button; the pinned
|
|
106
|
+
policy `narrowAmountPolicy` returns for a block is never an offer of the minimum.
|
|
107
|
+
- **`legalNote`** is a slot right above the buttons for the application's legal line (the EU
|
|
108
|
+
withdrawal note of a top-up, terms links) — `[data-legal-note]`.
|
|
109
|
+
- Selectors: `[data-amount-checkout]` (`data-blocked`), `[data-amount-preset="<minor>"]`,
|
|
110
|
+
`[data-amount-confirm]`.
|
|
111
|
+
|
|
96
112
|
## Price estimate: a live tax/currency read for any amount
|
|
97
113
|
|
|
98
114
|
```typescript
|
|
@@ -105,7 +121,7 @@ const estimate = usePriceEstimate(ctx.entrypoint(account.priceEstimate), { enabl
|
|
|
105
121
|
const [country, setCountry] = useState('')
|
|
106
122
|
const pro = usePriceEstimate(entry, { enabled, country, onCountryChange: setCountry }, { body: { planSku: 'pro' } })
|
|
107
123
|
const team = usePriceEstimate(entry, { enabled, country, onCountryChange: setCountry }, { body: { planSku: 'team' } })
|
|
108
|
-
<CountrySelect value={country} onChange={setCountry} />
|
|
124
|
+
<CountrySelect value={country} onChange={setCountry} disabled={pro.locked} /> {/* rendered ONCE */}
|
|
109
125
|
<PriceEstimateAmount control={pro} subtotalMinor={proPriceMinor} currency="usd" /> {/* per offer */}
|
|
110
126
|
<PriceEstimateAmount control={team} subtotalMinor={teamPriceMinor} currency="usd" />
|
|
111
127
|
```
|
|
@@ -122,9 +138,15 @@ const team = usePriceEstimate(entry, { enabled, country, onCountryChange: setCou
|
|
|
122
138
|
calls can share ONE value (a plan comparison table, one picker, one estimate per offer); the
|
|
123
139
|
hook never preselects on its own, and the returned control's `onCountryChange` calls
|
|
124
140
|
`opts.onCountryChange` instead of an internal setter.
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
- **Locked** (an answer with `locked: true` or `source: 'profile'` — the entity's billing country
|
|
142
|
+
fixed by its first purchase): the hook adopts that country over any pick (controlled: through
|
|
143
|
+
`opts.onCountryChange`, once), returns `locked: true`, and ignores `onCountryChange` from then
|
|
144
|
+
on (`isLockedAnswer(estimate)`).
|
|
145
|
+
- **`CountrySelect({ value, onChange, label?, id?, className?, disabled?, note? })`** is the picker
|
|
146
|
+
alone — `Intl.DisplayNames` + `Intl.Collator` over `@owlmeans/payment`'s `COUNTRY_CODES`, so it
|
|
147
|
+
lists the buyer's language's own country names. Render it once and drive every estimate that
|
|
148
|
+
shares it. `disabled` (a locked country) disables it; `note` is a line under it. Selectors
|
|
149
|
+
`[data-country-select]` with `data-locked` and `data-country`, `[data-country-note]`.
|
|
128
150
|
- **`PriceEstimateAmount({ control, subtotalMinor, currency, suffix?, className? })`** is the
|
|
129
151
|
numbers alone, no picker: via `estimateOf` re-derived against `subtotalMinor`, a rate row for a
|
|
130
152
|
`taxed` status, an estimated total, and one sentence for every status that leaves no number to
|
|
@@ -136,19 +158,114 @@ const team = usePriceEstimate(entry, { enabled, country, onCountryChange: setCou
|
|
|
136
158
|
at Stripe's current rate and confirmed at checkout. With no `local`, the amounts show in
|
|
137
159
|
`currency` (the integration currency) with no `≈` and no note.
|
|
138
160
|
- **`PriceEstimateSummary`** is `CountrySelect` bound to the control's own `country`/
|
|
139
|
-
`onCountryChange` followed by `PriceEstimateAmount` — the one-estimate composition.
|
|
161
|
+
`onCountryChange` followed by `PriceEstimateAmount` — the one-estimate composition. A `locked`
|
|
162
|
+
control renders the picker disabled with the "set by your first purchase" note. Building a
|
|
140
163
|
shared picker composes the two pieces separately instead, one `PriceEstimateAmount` per estimate.
|
|
141
164
|
|
|
165
|
+
## `./consumer` — the EU consumer-rights pieces
|
|
166
|
+
|
|
167
|
+
`import { … } from '@owlmeans/web-payment/consumer'` — a separate subpath, so an application that
|
|
168
|
+
never renders them needs neither their checkbox primitive nor its peer. Legal text comes from
|
|
169
|
+
`@owlmeans/payment`'s `payment-consumer-rights` copy (`consumerText`, `legalLabelsOf`,
|
|
170
|
+
`consentStatementOf` render the same strings the server records and mails); the interface text
|
|
171
|
+
comes from this package's `web-payment` resource. Neither carries product copy: the trader, the
|
|
172
|
+
plan title and the price line are the view's or the application's.
|
|
173
|
+
|
|
174
|
+
**The language rule.** A consent or statutory function is shown in the CONTRACT language — the
|
|
175
|
+
billing country's (`view.language`, `list.language`, the `language` prop) — with a toggle to the
|
|
176
|
+
interface language and back (`useShownLanguage`, `LanguageToggle`). The whole piece switches: the
|
|
177
|
+
legal copy through `legalTextOf(lng)`, the interface strings through `paymentTextOf(lng)` (both
|
|
178
|
+
read any language without i18next and without draining a bundle — `resolveI18nResource`), dates
|
|
179
|
+
and amounts through `Intl` in that language. The toggle alone is phrased in the INTERFACE language
|
|
180
|
+
("Show in German") — the reader who needs it reads that one. The links follow the shown language
|
|
181
|
+
(`links` prop: a record by language or a function such as `lng => linksOf(policy, lng)`; absent,
|
|
182
|
+
the view's own). What a person agrees to is what was on screen: toggling un-ticks the checkbox, and
|
|
183
|
+
the recorded body names the language shown.
|
|
184
|
+
|
|
185
|
+
| Piece | Props | Behaviour |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `PerformanceConsentDialog` | `open, onOpenChange, view: PerformanceConsentView \| null, onConfirm(body: PerformanceConsentBody), onDecline?, uiLanguage?, pending?, error?, links?, onWithdraw?, formatAmount?` | Title and intro (count, last deadline), the purchases with date, amount and last withdrawal day (`performance-consent.purchase`), ONE checkbox carrying the whole statement (`performance-consent.checkbox` with `view.trader`), **unchecked by default**; confirm ("Start now" / "Jetzt beginnen" …) disabled until it is ticked; decline ("Not now"); links Billing Terms · Withdrawal information · the withdrawal function (a button when `onWithdraw`, else the links' `withdrawalFunction` page). `onConfirm` gets `{ purchaseIds, textVersion, language: <shown>, acknowledged: true, uiLanguage }`. |
|
|
188
|
+
| `SubscriptionStartDialog` | `open, onOpenChange, view: SubscriptionStartView \| null, planTitle, price?: ReactNode, onConfirm(body: SubscriptionStartBody), onDecline?, uiLanguage?, pending?, error?, links?, onWithdraw?` | The same shape before a subscription checkout; confirm is "Continue to payment" — the step before the paygate's own order button. |
|
|
189
|
+
| `WithdrawalFunctionButton` | `language, onClick, uiLanguage?, disabled?, variant? = 'outline'` | The statutory entry ("Withdraw from contract here", "Vertrag widerrufen", "Renoncer au contrat ici", "Odstąp od umowy tutaj") in the contract language; the interface language's label as `title` when they differ. |
|
|
190
|
+
| `WithdrawalForm` / `WithdrawalDialog` | `mode: 'in-app' \| 'public', language, onSubmit(body: WithdrawalBody), list?, defaults?, uiLanguage?, pending?, error?, receipt?, formatAmount?` (+ `open, onOpenChange` for the dialog; `onClose` for the form) | Three steps. **Form**: in-app a contract picker over `list.candidates` (bought, paid, last day, estimated refund), publicly a contract-reference field — plus name and e-mail, NOTHING else is asked (a public form also carries the honeypot). **Review**: the summary and the statutory confirm ("Confirm withdrawal", "Widerruf bestätigen", "Confirmer la rétractation", "Potwierdź odstąpienie od umowy"). **Receipt** (whenever `receipt` is set): the received sentence with date AND time in UTC, reference, status, refund. The dialog does not clear the receipt the application holds — reset it on close. |
|
|
191
|
+
| `CancellationForm` | `mode, language, onSubmit(body: CancellationBody), defaults?, uiLanguage?, pending?, error?, receipt?, onPrint?, onClose?` | **Form**: kind (ordinary / extraordinary — the reason becomes required), name, contract or customer reference, effective (earliest / a date from today), e-mail, and a honeypot. **Summary** with the statutory confirm ("Cancel now", "Jetzt kündigen", "Notification de la résiliation", "Wypowiedz teraz"). **Receipt**: received date and time in UTC, the effective date when known, status, a print button (`onPrint`, default `window.print()`). |
|
|
192
|
+
|
|
193
|
+
- `error` is `true` (the generic sentence in the shown language) or the application's own node — a
|
|
194
|
+
`WithdrawalUnavailable` phrased by the app, say.
|
|
195
|
+
- **The honeypot** (`Honeypot`, internal): an input named `website`, off screen, `tabIndex={-1}`,
|
|
196
|
+
inside `aria-hidden`; only a filled one is sent (`body.honeypot`), and the server answers a bot
|
|
197
|
+
exactly like a person.
|
|
198
|
+
- **Bodies** carry `language` = the shown language; an in-app withdrawal sends `purchaseId` and
|
|
199
|
+
`contractRef`, a public one `contractRef` only.
|
|
200
|
+
|
|
201
|
+
### Hooks — protocol-bound, from `makeConsumerRightsProtocols`
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
usePerformanceConsent(view: RegisteredEntrypoint<{}, PerformanceConsentView>,
|
|
205
|
+
record: RegisteredEntrypoint<{ body: PerformanceConsentBody }, PerformanceConsentResponse>,
|
|
206
|
+
opts?: { enabled?, uiLanguage?, links?, onWithdraw?, onRecorded?(response) })
|
|
207
|
+
→ { view, required: boolean | null, ensure(opts?: { force? }): Promise<boolean>, refresh(), dialog }
|
|
208
|
+
PerformanceConsentProvider({ view, record, options?, children }) // ONE dialog for the app
|
|
209
|
+
useConsentGate() → { ensure, withConsent<T>(action): Promise<T>, required }
|
|
210
|
+
useSubscriptionStart(start: RegisteredEntrypoint<{ query: { planSku } }, SubscriptionStartView>,
|
|
211
|
+
requestStart: RegisteredEntrypoint<{ body: SubscriptionStartBody }, SubscriptionStartResponse>, opts?)
|
|
212
|
+
→ { ensure(planSku, { planTitle, price? }): Promise<string | null>, view, dialog }
|
|
213
|
+
useWithdrawal(withdrawals | null, withdraw, { enabled? }) → { list, load, submit, receipt, pending, error, reset, form }
|
|
214
|
+
useCancellation(cancel) → { submit, receipt, pending, error, reset, form }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- **`ensure()`** reads the view fresh: `true` when nothing is required, or once the consent was
|
|
218
|
+
confirmed AND recorded; `false` on a decline or a closed dialog. A failed read answers `true` —
|
|
219
|
+
the server's 428 stays the authority, and a flaky read never blocks work that needs no consent.
|
|
220
|
+
Concurrent calls share ONE pending answer (a burst of clicks opens one dialog). A record refused
|
|
221
|
+
with a 428 (a stale text version) reloads the view and keeps the dialog open.
|
|
222
|
+
- **`useSubscriptionStart().ensure`** answers the `startRequestId` to send with the checkout
|
|
223
|
+
(`CreateCheckoutBody.startRequestId`), `''` when none is needed (or the read failed — the
|
|
224
|
+
checkout's own refusal then decides), `null` on a decline.
|
|
225
|
+
- **`withConsent(action)`** runs the action; on a SPEND-consent refusal it asks with
|
|
226
|
+
`ensure({ force: true })` and retries exactly ONCE. A decline throws `ConsentDeclined`
|
|
227
|
+
(`isConsentDeclined`); a second refusal and every other failure propagate unchanged. Outside a
|
|
228
|
+
provider the gate passes through (`ensure` → `true`, refusals propagate).
|
|
229
|
+
- The asking state machine is `makeAsker` and the gate `makeConsentGate` (pure, React-free).
|
|
230
|
+
- `dialog` / `form` objects are spread into ONE piece each; `form.error` is a boolean, the raw
|
|
231
|
+
failure is `error`.
|
|
232
|
+
|
|
233
|
+
### Recognising a consent refusal
|
|
234
|
+
|
|
235
|
+
`isConsentRefusal(e)` — `consentRefusalOf(e) != null` (the class after the registry, or its marker
|
|
236
|
+
in `message`/`type`) OR `httpStatusOf(e) === 428` (`@owlmeans/api/status`: a production body is only
|
|
237
|
+
an incident id, so the status is all that is left). Read on the error AND on what it wraps —
|
|
238
|
+
`cause`, `error`, `original`, `originalError`, `inner`, `reason`, an aggregate's `errors`, and
|
|
239
|
+
strings — five levels deep with a cycle guard, so a planning `CommitFailed`
|
|
240
|
+
(`planning:commit-failed:<transition>:<the refusal's text>`) is recognised without depending on
|
|
241
|
+
planning. `consentRefusalKindOf(e)` → `'performance' | 'subscription-start' | 'unknown'` (a bare
|
|
242
|
+
428) `| null`; `isPerformanceConsentRefusal(e)` is what the gate retries on (performance or
|
|
243
|
+
unknown — never a subscription start).
|
|
244
|
+
|
|
245
|
+
### Selectors
|
|
246
|
+
|
|
247
|
+
| Piece | Selectors |
|
|
248
|
+
|---|---|
|
|
249
|
+
| Spend consent | `[data-consent-dialog]` (also `[data-performance-consent]`) with `data-language` (shown), `data-contract-language`, `data-text-version`; `[data-consent-purchase]` with `data-purchase-id`, `data-deadline` (ISO); `[data-consent-statement]`; `[data-consent-checkbox]` (Radix `data-state`); `[data-consent-confirm]`; `[data-consent-decline]`; `[data-consent-language-toggle]` (also `[data-language-toggle]` with `data-target-language`) |
|
|
250
|
+
| Legal links (both dialogs) | `[data-legal-links]`, `[data-legal-link="billing-terms" \| "withdrawal-information" \| "withdrawal-function"]` |
|
|
251
|
+
| Subscription start | `[data-start-dialog]` (also `[data-subscription-start]`) with `data-language`, `data-contract-language`, `data-plan-sku`; `[data-start-statement]`, `[data-start-price]`, `[data-start-checkbox]`, `[data-start-confirm]`, `[data-start-decline]`, `[data-start-language-toggle]` |
|
|
252
|
+
| Withdrawal | `[data-withdrawal-function]` (`data-language`); `[data-withdrawal-dialog]` (`data-language`, `data-step`); `[data-withdrawal-form]` with `data-withdrawal-step="form\|review\|receipt"` (also `data-step`, `data-mode`, `data-language`); `[data-withdrawal-candidate]` (`data-purchase-id`, `data-contract-ref`) and its radio `[data-withdrawal-candidate-input="<purchaseId>"]`; `[data-withdrawal-estimate]` (`data-refund-minor`); `[data-withdrawal-field="contract\|name\|email"]`; `[data-withdrawal-continue]`, `[data-withdrawal-back]`, `[data-withdrawal-confirm]`; `[data-withdrawal-receipt]` (`data-status`, `data-declaration-id`, `data-received-at`), `[data-withdrawal-reference]`, `[data-withdrawal-received-at]`, `[data-withdrawal-refund]`; `[data-withdrawal-language-toggle]` |
|
|
253
|
+
| Cancellation | `[data-cancellation-form]` with `data-cancellation-step="form\|review\|receipt"` (also `data-step`, `data-mode`, `data-language`); `[data-cancellation-field="kind\|reason\|name\|contract\|effective\|date\|email"]` (kind radios carry `data-kind`, effective radios `data-effective`); `[data-cancellation-continue]`, `[data-cancellation-back]`, `[data-cancellation-confirm]`, `[data-cancellation-summary]`; `[data-cancellation-receipt]` (`data-status`, `data-declaration-id`, `data-received-at`, `data-effective-at`), `[data-cancellation-effective]`, `[data-cancellation-print]`; `[data-cancellation-language-toggle]` |
|
|
254
|
+
| Honeypot | `[data-honeypot]` |
|
|
255
|
+
|
|
142
256
|
## The `@/components/ui/*` contract
|
|
143
257
|
|
|
144
258
|
Components import the consuming app's shadcn primitives through the app alias:
|
|
145
|
-
`@/components/ui/{button,dialog,input,label,select}` for the dialog and estimate summary
|
|
259
|
+
`@/components/ui/{button,dialog,input,label,select}` for the dialog and estimate summary,
|
|
146
260
|
`@/components/ui/{card,progress,button}` for the pieces (`Card`, `CardHeader`, `CardTitle`,
|
|
147
|
-
`CardDescription`, `CardContent`, `CardFooter`, `Progress`)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
261
|
+
`CardDescription`, `CardContent`, `CardFooter`, `Progress`), and — **only from `./consumer`** —
|
|
262
|
+
`@/components/ui/checkbox` (`export { Checkbox }` with the Radix Root props, the stock shadcn file).
|
|
263
|
+
The consumer therefore provides those files and the peers `@radix-ui/react-progress` /
|
|
264
|
+
`@radix-ui/react-select` beside the dialog's Radix ones, plus `@radix-ui/react-checkbox` when it
|
|
265
|
+
imports `./consumer`. The package carries standalone copies under `src/@` only so its own build and
|
|
266
|
+
tests resolve the same alias; they are never exported (its checkbox draws an inline SVG check, no
|
|
267
|
+
icon library). Styling uses semantic theme tokens only (`primary`, `muted-foreground`,
|
|
268
|
+
`destructive`, `border`, `card`).
|
|
152
269
|
|
|
153
270
|
A Tailwind v4 consumer scanning package source adds:
|
|
154
271
|
|
|
@@ -158,18 +275,41 @@ A Tailwind v4 consumer scanning package source adds:
|
|
|
158
275
|
|
|
159
276
|
## Strings
|
|
160
277
|
|
|
161
|
-
All visible strings live in the `web-payment` library resource (`lib` namespace
|
|
162
|
-
languages — `en pl ru be uk es de` — under
|
|
163
|
-
|
|
164
|
-
`
|
|
165
|
-
|
|
166
|
-
|
|
278
|
+
All visible interface strings live in the `web-payment` library resource (`lib` namespace,
|
|
279
|
+
`WEB_PAYMENT_RESOURCE`) in all eight languages — `en pl ru be uk es de fr` — under
|
|
280
|
+
`amount-checkout` (incl. `above-limit`), `checkout-limit` (`note`, `blocked`, `resets`,
|
|
281
|
+
`reason.{per-purchase,window,hold,other}`), `entitlement` (`plan`, `status`, `limit`, `promo`,
|
|
282
|
+
`capability`), `estimate` (`country`, `country-locked`, `tax-type`, `rate`, `total`,
|
|
283
|
+
`converted-note`, `status`, `loading`, `failed`), and for `./consumer`: `consumer` (toggle, steps,
|
|
284
|
+
receipt labels, validation), `withdrawal` (candidate lines, estimate, kinds, `status.*`) and
|
|
285
|
+
`cancellation` (`effective-at`, `status.*`). The legal copy is NOT here — it is `@owlmeans/payment`'s.
|
|
286
|
+
|
|
287
|
+
- A new key lands in every language at once, with the same `{{placeholders}}`; the parity spec
|
|
288
|
+
walks the resources recursively and scans every language for the forbidden wording (never
|
|
289
|
+
"non-refundable" or a translation of it: the right of withdrawal EXPIRES for what was used, and
|
|
290
|
+
unused credits ARE reimbursed). Every `WithdrawalStatus`, `CancellationStatus` (incl. `review` —
|
|
291
|
+
extraordinary and unmatched public cancellations) and `PurchaseKind` value has its string; the
|
|
292
|
+
spec iterates the enums, so a new status fails it until all eight languages phrase it.
|
|
293
|
+
- The last withdrawal day shown anywhere is `@owlmeans/payment`'s `lastWithdrawalDayOf(deadline)`
|
|
294
|
+
(the deadline is exclusive), formatted as a UTC date — the rule the server's e-mails use.
|
|
295
|
+
- The `consumer`/`withdrawal`/`cancellation` branches take the register of the legal copy they sit
|
|
296
|
+
beside (de and es formal, like `payment-consumer-rights`), while `amount-checkout` and
|
|
297
|
+
`checkout-limit` keep the dialog's.
|
|
298
|
+
- An application overrides a string with `addI18nApp(lng, 'web-payment', data, { ns: LIB_NAMESPACE })`
|
|
299
|
+
— `paymentTextOf` reads that override too.
|
|
167
300
|
|
|
168
301
|
## Tests
|
|
169
302
|
|
|
170
|
-
Category D plus unit specs: `bun test ./tests` runs the selector, hook
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
303
|
+
Category D plus unit specs: `bun test ./tests` runs the selector, hook, cache, consent-refusal
|
|
304
|
+
(`consent-refusal.spec.ts`: class, marker in message/type/string, bare 428 `ApiStatusError`,
|
|
305
|
+
stamped status, `cause`/`error`/aggregate nesting, a `CommitFailed`-shaped message, negatives) and
|
|
306
|
+
asker/gate (`consumer-hooks.spec.ts`: `makeAsker` sharing and failing open, `withConsent` retrying
|
|
307
|
+
exactly once, first-render hook shapes with `react-dom/server`) specs, the 8-language parity spec,
|
|
308
|
+
and drives chromium through the Vite harness, which picks a surface by `?case=` (none = the dialog,
|
|
309
|
+
`pieces`, `hook`, `shared-estimate`; `consent`, `start`, `withdrawal`, `withdrawal-public`,
|
|
310
|
+
`cancellation`, `limit` (`&blocked=true`), `country-locked`, `consent-gate`), with `?lng=` the
|
|
311
|
+
contract language and `?ui=` the interface language, and for the dialog an `?estimate=<name>`
|
|
312
|
+
canned `PriceEstimateControl` fixture (`tests/harness/estimate-fixtures.ts`,
|
|
313
|
+
`tests/harness/consumer-fixtures.ts` — no server, no Stripe). The harness pre-bundles every runtime
|
|
314
|
+
dependency in `optimizeDeps.include` (`@radix-ui/react-checkbox` included); one discovered
|
|
315
|
+
mid-navigation loads a second React and the page throws "Invalid hook call".
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
3
|
+
/** The shadcn checkbox, with an inline check mark instead of an icon library. */
|
|
4
|
+
export declare const Checkbox: ({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) => React.JSX.Element;
|
|
5
|
+
//# sourceMappingURL=checkbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../../src/@/components/ui/checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAG7D,iFAAiF;AACjF,eAAO,MAAM,QAAQ,4BAA6B,KAAK,CAAC,cAAc,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,sBAiB1E,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
/** The shadcn checkbox, with an inline check mark instead of an icon library. */
|
|
5
|
+
export const Checkbox = ({ className, ...props }) => _jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn('peer border-input size-4 shrink-0 cursor-pointer rounded-[4px] border shadow-xs outline-none transition-shadow', 'data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary', 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "flex items-center justify-center text-current", children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", className: "size-3.5", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) });
|
|
6
|
+
//# sourceMappingURL=checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkbox.js","sourceRoot":"","sources":["../../../../src/@/components/ui/checkbox.tsx"],"names":[],"mappings":";AACA,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAEhC,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAuD,EAAE,EAAE,CACvG,KAAC,iBAAiB,CAAC,IAAI,iBACX,UAAU,EACpB,SAAS,EAAE,EAAE,CACX,gHAAgH,EAChH,kHAAkH,EAClH,+HAA+H,EAC/H,SAAS,CACV,KACG,KAAK,YAET,KAAC,iBAAiB,CAAC,SAAS,iBAAW,oBAAoB,EAAC,SAAS,EAAC,+CAA+C,YACnH,cAAK,OAAO,EAAC,WAAW,iBAAa,MAAM,EAAC,SAAS,EAAC,UAAU,EAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAC,cAAc,EAChG,WAAW,EAAE,CAAC,EAAE,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,YAC5D,eAAM,CAAC,EAAC,iBAAiB,GAAG,GACxB,GACsB,GACP,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CheckoutLimitNoteProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What an amount checkout is narrowed to for this entity now: the largest amount (or that nothing
|
|
4
|
+
* may be bought), why, and when it rises again — the same `CheckoutLimitView` the server refuses
|
|
5
|
+
* with, so the note and a refusal never disagree.
|
|
6
|
+
*
|
|
7
|
+
* `[data-checkout-limit]` carries `data-max-minor`, `data-blocked`, `data-reason` and, when known,
|
|
8
|
+
* `data-resets-at` (ISO).
|
|
9
|
+
*/
|
|
10
|
+
export declare const CheckoutLimitNote: ({ limit, reasonLabel, className }: CheckoutLimitNoteProps) => import("react").JSX.Element | null;
|
|
11
|
+
//# sourceMappingURL=checkout-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout-limit.d.ts","sourceRoot":"","sources":["../src/checkout-limit.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAKxD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,sCAAuC,sBAAsB,uCA+B1F,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useI18nLib, useLanguage } from '@owlmeans/client-i18n';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
import { money, shortMomentUtc } from './format.js';
|
|
5
|
+
/** The reasons the package phrases itself; any other reads the generic sentence. */
|
|
6
|
+
const KNOWN_REASONS = ['per-purchase', 'window', 'hold'];
|
|
7
|
+
/**
|
|
8
|
+
* What an amount checkout is narrowed to for this entity now: the largest amount (or that nothing
|
|
9
|
+
* may be bought), why, and when it rises again — the same `CheckoutLimitView` the server refuses
|
|
10
|
+
* with, so the note and a refusal never disagree.
|
|
11
|
+
*
|
|
12
|
+
* `[data-checkout-limit]` carries `data-max-minor`, `data-blocked`, `data-reason` and, when known,
|
|
13
|
+
* `data-resets-at` (ISO).
|
|
14
|
+
*/
|
|
15
|
+
export const CheckoutLimitNote = ({ limit, reasonLabel, className }) => {
|
|
16
|
+
const t = useI18nLib('web-payment', 'checkout-limit');
|
|
17
|
+
const [locale] = useLanguage();
|
|
18
|
+
if (limit == null || (!limit.narrowed && !limit.blocked)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const reason = limit.reason ?? '';
|
|
22
|
+
const custom = typeof reasonLabel === 'function' ? reasonLabel(reason) : reasonLabel;
|
|
23
|
+
const explained = custom != null && custom !== ''
|
|
24
|
+
? custom
|
|
25
|
+
: t(`reason.${KNOWN_REASONS.includes(reason) ? reason : 'other'}`);
|
|
26
|
+
const resetsAt = limit.resetsAt != null ? new Date(limit.resetsAt) : null;
|
|
27
|
+
const resets = resetsAt != null && !Number.isNaN(resetsAt.getTime())
|
|
28
|
+
? t('resets', { date: shortMomentUtc(resetsAt, locale), interpolation: { escapeValue: false } })
|
|
29
|
+
: null;
|
|
30
|
+
return _jsxs("div", { "data-checkout-limit": "", "data-max-minor": limit.maximumMinor, "data-blocked": limit.blocked ? 'true' : 'false', "data-reason": reason, "data-resets-at": resetsAt?.toISOString(), role: limit.blocked ? 'alert' : 'note', className: cn('grid gap-1 rounded-lg border p-3 text-sm', limit.blocked ? 'border-destructive/50' : 'bg-muted/30', className), children: [_jsx("p", { className: limit.blocked ? 'text-destructive font-medium' : 'font-medium', "data-checkout-limit-headline": "", children: limit.blocked
|
|
31
|
+
? t('blocked')
|
|
32
|
+
: t('note', { amount: money(limit.maximumMinor, limit.currency, locale), interpolation: { escapeValue: false } }) }), _jsx("p", { className: "text-muted-foreground text-xs", "data-checkout-limit-reason": "", children: explained }), resets != null && _jsx("p", { className: "text-muted-foreground text-xs", "data-checkout-limit-resets": "", children: resets })] });
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=checkout-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout-limit.js","sourceRoot":"","sources":["../src/checkout-limit.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CancellationFormProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The cancellation function (§ 312k BGB "Verträge hier kündigen" → "Jetzt kündigen"; L215-1-1 /
|
|
4
|
+
* D215-1 "Résilier votre contrat" → a summary → "Notification de la résiliation"), for a public
|
|
5
|
+
* page reachable without a login and for the same function in-app.
|
|
6
|
+
*
|
|
7
|
+
* Step 1 asks for the kind (ordinary, or extraordinary with its reason), the name, the contract or
|
|
8
|
+
* customer reference, the date (the earliest possible, or a specific one) and the e-mail address
|
|
9
|
+
* the acknowledgement goes to; a honeypot field no person reaches rides along. Step 2 is the
|
|
10
|
+
* summary with the statutory confirm button; step 3 the receipt — date and time of receipt in UTC,
|
|
11
|
+
* the effective date when known — with a print button, so it can be kept.
|
|
12
|
+
*
|
|
13
|
+
* Selectors: `[data-cancellation-form]` / `[data-cancellation-step="form|review|receipt"]` (also
|
|
14
|
+
* `data-step`, `data-language`, `data-mode`), `[data-cancellation-field="kind|reason|name|contract|
|
|
15
|
+
* effective|date|email"]`, `[data-cancellation-continue]`, `[data-cancellation-confirm]`,
|
|
16
|
+
* `[data-cancellation-receipt]` (`data-status`, `data-declaration-id`, `data-received-at`,
|
|
17
|
+
* `data-effective-at`), `[data-cancellation-print]`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CancellationForm: ({ mode, language: contractLanguage, uiLanguage, defaults, pending, error, receipt, onSubmit, onPrint, onClose, className, }: CancellationFormProps) => import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=cancellation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../src/consumer/cancellation.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,qBAAqB,EAAmB,MAAM,YAAY,CAAA;AAOxE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,gIAG1B,qBAAqB,gCAiLvB,CAAA"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { CancellationKind } from '@owlmeans/payment';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { dayUtc, momentUtc } from '../format.js';
|
|
7
|
+
import { useLegalText, usePaymentText } from './copy.js';
|
|
8
|
+
import { Choice, Field, Honeypot, isEmail } from './fields.js';
|
|
9
|
+
import { ErrorLine, LanguageToggle, useShownLanguage } from './legal.js';
|
|
10
|
+
const isCancellationReceipt = (receipt) => 'status' in receipt;
|
|
11
|
+
/** Today as a UTC calendar day, `YYYY-MM-DD` — the earliest date a termination may name. */
|
|
12
|
+
const todayUtc = () => new Date().toISOString().slice(0, 10);
|
|
13
|
+
/**
|
|
14
|
+
* The cancellation function (§ 312k BGB "Verträge hier kündigen" → "Jetzt kündigen"; L215-1-1 /
|
|
15
|
+
* D215-1 "Résilier votre contrat" → a summary → "Notification de la résiliation"), for a public
|
|
16
|
+
* page reachable without a login and for the same function in-app.
|
|
17
|
+
*
|
|
18
|
+
* Step 1 asks for the kind (ordinary, or extraordinary with its reason), the name, the contract or
|
|
19
|
+
* customer reference, the date (the earliest possible, or a specific one) and the e-mail address
|
|
20
|
+
* the acknowledgement goes to; a honeypot field no person reaches rides along. Step 2 is the
|
|
21
|
+
* summary with the statutory confirm button; step 3 the receipt — date and time of receipt in UTC,
|
|
22
|
+
* the effective date when known — with a print button, so it can be kept.
|
|
23
|
+
*
|
|
24
|
+
* Selectors: `[data-cancellation-form]` / `[data-cancellation-step="form|review|receipt"]` (also
|
|
25
|
+
* `data-step`, `data-language`, `data-mode`), `[data-cancellation-field="kind|reason|name|contract|
|
|
26
|
+
* effective|date|email"]`, `[data-cancellation-continue]`, `[data-cancellation-confirm]`,
|
|
27
|
+
* `[data-cancellation-receipt]` (`data-status`, `data-declaration-id`, `data-received-at`,
|
|
28
|
+
* `data-effective-at`), `[data-cancellation-print]`.
|
|
29
|
+
*/
|
|
30
|
+
export const CancellationForm = ({ mode, language: contractLanguage, uiLanguage, defaults, pending = false, error, receipt, onSubmit, onPrint, onClose, className, }) => {
|
|
31
|
+
const language = useShownLanguage(contractLanguage, uiLanguage);
|
|
32
|
+
const legal = useLegalText(language.shown);
|
|
33
|
+
const text = usePaymentText(language.shown);
|
|
34
|
+
const [step, setStep] = useState('form');
|
|
35
|
+
const [kind, setKind] = useState(CancellationKind.Ordinary);
|
|
36
|
+
const [reason, setReason] = useState('');
|
|
37
|
+
const [name, setName] = useState(defaults?.name ?? '');
|
|
38
|
+
const [contractRef, setContractRef] = useState(defaults?.contractRef ?? '');
|
|
39
|
+
const [effective, setEffective] = useState('earliest');
|
|
40
|
+
const [date, setDate] = useState('');
|
|
41
|
+
const [email, setEmail] = useState(defaults?.email ?? '');
|
|
42
|
+
const [honeypot, setHoneypot] = useState('');
|
|
43
|
+
const [checked, setChecked] = useState(false);
|
|
44
|
+
const [submitted, setSubmitted] = useState(null);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (receipt == null && submitted != null)
|
|
47
|
+
setStep('form');
|
|
48
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
49
|
+
}, [receipt]);
|
|
50
|
+
const current = receipt != null ? 'receipt' : step;
|
|
51
|
+
const extraordinary = kind === CancellationKind.Extraordinary;
|
|
52
|
+
const earliestDate = todayUtc();
|
|
53
|
+
const errors = {
|
|
54
|
+
reason: extraordinary && reason.trim() === '' ? text('consumer.required') : null,
|
|
55
|
+
name: name.trim() === '' ? text('consumer.required') : null,
|
|
56
|
+
date: effective === 'date' && (!/^\d{4}-\d{2}-\d{2}$/.test(date) || date < earliestDate) ? text('consumer.date-invalid') : null,
|
|
57
|
+
email: email.trim() === '' ? text('consumer.required') : !isEmail(email.trim()) ? text('consumer.email-invalid') : null,
|
|
58
|
+
};
|
|
59
|
+
const shown = (key) => checked ? errors[key] : null;
|
|
60
|
+
const valid = Object.values(errors).every(value => value == null);
|
|
61
|
+
const body = () => ({
|
|
62
|
+
kind,
|
|
63
|
+
...(extraordinary ? { reason: reason.trim() } : {}),
|
|
64
|
+
name: name.trim(),
|
|
65
|
+
...(contractRef.trim() !== '' ? { contractRef: contractRef.trim() } : {}),
|
|
66
|
+
...(defaults?.subscriptionId != null ? { subscriptionId: defaults.subscriptionId } : {}),
|
|
67
|
+
effective,
|
|
68
|
+
...(effective === 'date' ? { date } : {}),
|
|
69
|
+
email: email.trim(),
|
|
70
|
+
language: language.shown,
|
|
71
|
+
...(honeypot !== '' ? { honeypot } : {}),
|
|
72
|
+
});
|
|
73
|
+
const toReview = () => {
|
|
74
|
+
setChecked(true);
|
|
75
|
+
if (valid)
|
|
76
|
+
setStep('review');
|
|
77
|
+
};
|
|
78
|
+
const confirm = async () => {
|
|
79
|
+
if (!valid || pending)
|
|
80
|
+
return;
|
|
81
|
+
const declared = body();
|
|
82
|
+
setSubmitted(declared);
|
|
83
|
+
await onSubmit(declared);
|
|
84
|
+
};
|
|
85
|
+
const print = () => {
|
|
86
|
+
if (onPrint != null)
|
|
87
|
+
onPrint();
|
|
88
|
+
else if (typeof window !== 'undefined')
|
|
89
|
+
window.print();
|
|
90
|
+
};
|
|
91
|
+
const kindLabel = (value) => legal(value === CancellationKind.Extraordinary
|
|
92
|
+
? 'cancellation.kind-extraordinary' : 'cancellation.kind-ordinary');
|
|
93
|
+
const effectiveLabel = (declared) => declared.effective === 'date' && declared.date != null
|
|
94
|
+
? dayUtc(`${declared.date}T00:00:00Z`, language.shown) : legal('cancellation.effective-earliest');
|
|
95
|
+
const summary = (declared, hook) => _jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 rounded-lg border bg-muted/30 p-4 text-sm", ...(hook != null ? { [hook]: '' } : {}), children: [_jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.kind') }), _jsx("dd", { "data-summary": "kind", children: kindLabel(declared.kind) }), declared.reason != null && _jsxs(_Fragment, { children: [_jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.reason') }), _jsx("dd", { "data-summary": "reason", className: "whitespace-pre-wrap", children: declared.reason })] }), _jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.name') }), _jsx("dd", { "data-summary": "name", children: declared.name }), _jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.contract') }), _jsx("dd", { "data-summary": "contract", children: declared.contractRef ?? '—' }), _jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.effective') }), _jsx("dd", { "data-summary": "effective", children: effectiveLabel(declared) }), _jsx("dt", { className: "text-muted-foreground", children: legal('cancellation.email') }), _jsx("dd", { "data-summary": "email", children: declared.email })] });
|
|
96
|
+
let content;
|
|
97
|
+
if (current === 'receipt' && receipt != null) {
|
|
98
|
+
const receivedAt = new Date(receipt.receivedAt);
|
|
99
|
+
const declared = submitted ?? body();
|
|
100
|
+
const status = isCancellationReceipt(receipt) ? receipt.status : undefined;
|
|
101
|
+
const effectiveAt = isCancellationReceipt(receipt) && receipt.effectiveAt != null ? new Date(receipt.effectiveAt) : null;
|
|
102
|
+
const effectiveKnown = effectiveAt != null && !Number.isNaN(effectiveAt.getTime());
|
|
103
|
+
content = _jsxs("div", { className: "grid gap-4", "data-cancellation-receipt": "", "data-status": status ?? 'received', "data-declaration-id": receipt.declarationId, "data-received-at": Number.isNaN(receivedAt.getTime()) ? undefined : receivedAt.toISOString(), "data-effective-at": effectiveKnown ? effectiveAt.toISOString() : undefined, children: [_jsx("h3", { className: "text-sm font-medium", children: text('consumer.receipt') }), _jsx("p", { className: "text-sm", children: receipt.mailed
|
|
104
|
+
? legal('cancellation.received', { date: momentUtc(receivedAt, language.shown), email: declared.email })
|
|
105
|
+
: `${text('consumer.received-at')}: ${momentUtc(receivedAt, language.shown)} (UTC). ${text('consumer.not-mailed')}` }), effectiveKnown && _jsx("p", { className: "text-sm font-medium", "data-cancellation-effective": "", children: text('cancellation.effective-at', { date: dayUtc(effectiveAt, language.shown) }) }), _jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 rounded-lg border p-4 text-sm", children: [_jsx("dt", { className: "text-muted-foreground", children: text('consumer.reference') }), _jsx("dd", { "data-cancellation-reference": "", children: receipt.declarationId }), _jsx("dt", { className: "text-muted-foreground", children: text('consumer.received-at') }), _jsxs("dd", { "data-cancellation-received-at": "", children: [momentUtc(receivedAt, language.shown), " (UTC)"] }), status != null && _jsxs(_Fragment, { children: [_jsx("dt", { className: "text-muted-foreground", children: text('consumer.status') }), _jsx("dd", { "data-cancellation-status": "", children: text(`cancellation.status.${status}`) })] })] }), summary(declared), _jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end print:hidden", children: [onClose != null && _jsx(Button, { type: "button", variant: "outline", onClick: onClose, children: text('consumer.close') }), _jsx(Button, { type: "button", variant: "outline", onClick: print, "data-cancellation-print": "", children: text('consumer.print') })] })] });
|
|
106
|
+
}
|
|
107
|
+
else if (current === 'review') {
|
|
108
|
+
content = _jsxs("div", { className: "grid gap-4", children: [_jsx("h3", { className: "text-sm font-medium", children: text('consumer.review') }), _jsx("p", { className: "text-sm", children: legal('cancellation.summary') }), summary(body(), 'data-cancellation-summary'), _jsx(ErrorLine, { error: error, text: text }), _jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [_jsx(Button, { type: "button", variant: "outline", disabled: pending, onClick: () => setStep('form'), "data-cancellation-back": "", children: text('consumer.back') }), _jsx(Button, { type: "button", disabled: pending || !valid, onClick: () => void confirm(), "data-cancellation-confirm": "", children: pending ? text('consumer.pending') : legal('cancellation.confirm') })] })] });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
content = _jsxs("form", { className: "relative grid gap-4", noValidate: true, onSubmit: event => { event.preventDefault(); toReview(); }, children: [_jsxs("fieldset", { className: "grid gap-2", children: [_jsx("legend", { className: "mb-1 text-sm font-medium", children: legal('cancellation.kind') }), [CancellationKind.Ordinary, CancellationKind.Extraordinary].map(value => _jsx(Choice, { id: `cancellation-kind-${value}`, name: "cancellation-kind", checked: kind === value, onSelect: () => setKind(value), disabled: pending, hook: { 'data-cancellation-field': 'kind', 'data-kind': value }, children: kindLabel(value) }, value))] }), extraordinary && _jsx(Field, { id: "cancellation-reason", label: legal('cancellation.reason'), value: reason, onChange: setReason, error: shown('reason'), disabled: pending, multiline: true, hook: { 'data-cancellation-field': 'reason' } }), _jsx(Field, { id: "cancellation-name", label: legal('cancellation.name'), value: name, onChange: setName, error: shown('name'), autoComplete: "name", disabled: pending, hook: { 'data-cancellation-field': 'name' } }), _jsx(Field, { id: "cancellation-contract", label: legal('cancellation.contract'), value: contractRef, onChange: setContractRef, disabled: pending, hook: { 'data-cancellation-field': 'contract' } }), _jsxs("fieldset", { className: "grid gap-2", children: [_jsx("legend", { className: "mb-1 text-sm font-medium", children: legal('cancellation.effective') }), _jsx(Choice, { id: "cancellation-effective-earliest", name: "cancellation-effective", checked: effective === 'earliest', onSelect: () => setEffective('earliest'), disabled: pending, hook: { 'data-cancellation-field': 'effective', 'data-effective': 'earliest' }, children: legal('cancellation.effective-earliest') }), _jsx(Choice, { id: "cancellation-effective-date", name: "cancellation-effective", checked: effective === 'date', onSelect: () => setEffective('date'), disabled: pending, hook: { 'data-cancellation-field': 'effective', 'data-effective': 'date' }, children: legal('cancellation.effective-date') }), effective === 'date' && _jsx(Field, { id: "cancellation-date", type: "date", label: legal('cancellation.effective-date'), value: date, onChange: setDate, min: earliestDate, error: shown('date'), disabled: pending, hook: { 'data-cancellation-field': 'date' } })] }), _jsx(Field, { id: "cancellation-email", type: "email", label: legal('cancellation.email'), value: email, onChange: setEmail, error: shown('email'), autoComplete: "email", disabled: pending, hook: { 'data-cancellation-field': 'email' } }), _jsx(Honeypot, { id: "cancellation-website", label: text('consumer.website'), value: honeypot, onChange: setHoneypot }), _jsx(ErrorLine, { error: error, text: text }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", disabled: pending, "data-cancellation-continue": "", children: text('consumer.continue') }) })] });
|
|
112
|
+
}
|
|
113
|
+
return _jsxs("section", { className: cn('grid gap-4', className), "data-cancellation-section": "", "data-language": language.shown, children: [_jsxs("header", { className: "grid gap-1.5", lang: language.shown, children: [_jsx("h2", { className: "text-lg font-semibold leading-none", children: legal('cancellation.title') }), current === 'form' && _jsx("p", { className: "text-muted-foreground text-sm", children: legal('cancellation.intro') })] }), _jsx(LanguageToggle, { language: language, hook: "data-cancellation-language-toggle" }), _jsx("div", { "data-cancellation-form": "", "data-cancellation-step": current, "data-step": current, "data-mode": mode, "data-language": language.shown, lang: language.shown, className: "grid gap-4", children: content })] });
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=cancellation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancellation.js","sourceRoot":"","sources":["../../src/consumer/cancellation.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CopyValues } from '@owlmeans/payment';
|
|
2
|
+
/** A translator bound to ONE language, whatever the active i18n language is. */
|
|
3
|
+
export type FixedText = (key: string, values?: CopyValues) => string;
|
|
4
|
+
/**
|
|
5
|
+
* The package's own interface strings (`web-payment`, library tier, any application override
|
|
6
|
+
* included) in a FIXED language, merged key by key over English — no i18next instance and no
|
|
7
|
+
* draining (`resolveI18nResource`). What lets a consumer-rights dialog render wholly in the
|
|
8
|
+
* contract language while the rest of the page stays in the interface language.
|
|
9
|
+
*/
|
|
10
|
+
export declare const paymentTextOf: (lng: string) => FixedText;
|
|
11
|
+
/**
|
|
12
|
+
* The legal copy of `@owlmeans/payment` (`payment-consumer-rights`) in a fixed language. A text
|
|
13
|
+
* that cannot be rendered in the language (a broken application override) falls back to English
|
|
14
|
+
* rather than blanking the page; one that cannot be rendered at all is `''`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const legalTextOf: (lng: string) => FixedText;
|
|
17
|
+
/** `paymentTextOf(lng)`, memoised for the language. */
|
|
18
|
+
export declare const usePaymentText: (lng: string) => FixedText;
|
|
19
|
+
/** `legalTextOf(lng)`, memoised for the language. */
|
|
20
|
+
export declare const useLegalText: (lng: string) => FixedText;
|
|
21
|
+
/** A language's own name in another language (`de` in `en` → "German"); the code when unknown. */
|
|
22
|
+
export declare const languageNameOf: (lng: string, inLanguage: string) => string;
|
|
23
|
+
//# sourceMappingURL=copy.d.ts.map
|