@owlmeans/payment 0.1.18-rc.37 → 0.1.18-rc.38
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/entitlements/SKILL.md +4 -0
- package/agent-meta/skills/payment/SKILL.md +217 -13
- package/build/advertise.js +5 -3
- package/build/advertise.js.map +1 -1
- package/build/checkout/index.d.ts +3 -0
- package/build/checkout/index.d.ts.map +1 -0
- package/build/checkout/index.js +3 -0
- package/build/checkout/index.js.map +1 -0
- package/build/checkout/narrow.d.ts +25 -0
- package/build/checkout/narrow.d.ts.map +1 -0
- package/build/checkout/narrow.js +58 -0
- package/build/checkout/narrow.js.map +1 -0
- package/build/checkout/protocols.d.ts +32 -0
- package/build/checkout/protocols.d.ts.map +1 -0
- package/build/checkout/protocols.js +28 -0
- package/build/checkout/protocols.js.map +1 -0
- package/build/consts.d.ts +87 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +127 -0
- package/build/consts.js.map +1 -1
- package/build/consumer/cancel.d.ts +11 -0
- package/build/consumer/cancel.d.ts.map +1 -0
- package/build/consumer/cancel.js +33 -0
- package/build/consumer/cancel.js.map +1 -0
- package/build/consumer/copy.d.ts +58 -0
- package/build/consumer/copy.d.ts.map +1 -0
- package/build/consumer/copy.js +91 -0
- package/build/consumer/copy.js.map +1 -0
- package/build/consumer/deadline.d.ts +43 -0
- package/build/consumer/deadline.d.ts.map +1 -0
- package/build/consumer/deadline.js +55 -0
- package/build/consumer/deadline.js.map +1 -0
- package/build/consumer/fifo.d.ts +40 -0
- package/build/consumer/fifo.d.ts.map +1 -0
- package/build/consumer/fifo.js +40 -0
- package/build/consumer/fifo.js.map +1 -0
- package/build/consumer/index.d.ts +10 -0
- package/build/consumer/index.d.ts.map +1 -0
- package/build/consumer/index.js +10 -0
- package/build/consumer/index.js.map +1 -0
- package/build/consumer/policy.d.ts +39 -0
- package/build/consumer/policy.d.ts.map +1 -0
- package/build/consumer/policy.js +122 -0
- package/build/consumer/policy.js.map +1 -0
- package/build/consumer/protocols.d.ts +105 -0
- package/build/consumer/protocols.d.ts.map +1 -0
- package/build/consumer/protocols.js +73 -0
- package/build/consumer/protocols.js.map +1 -0
- package/build/consumer/refund.d.ts +74 -0
- package/build/consumer/refund.d.ts.map +1 -0
- package/build/consumer/refund.js +123 -0
- package/build/consumer/refund.js.map +1 -0
- package/build/consumer/refusal.d.ts +10 -0
- package/build/consumer/refusal.d.ts.map +1 -0
- package/build/consumer/refusal.js +40 -0
- package/build/consumer/refusal.js.map +1 -0
- package/build/consumer/revive.d.ts +13 -0
- package/build/consumer/revive.d.ts.map +1 -0
- package/build/consumer/revive.js +26 -0
- package/build/consumer/revive.js.map +1 -0
- package/build/errors.d.ts +129 -0
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +221 -0
- package/build/errors.js.map +1 -1
- package/build/i18n/consumer-rights/be.json +123 -0
- package/build/i18n/consumer-rights/de.json +123 -0
- package/build/i18n/consumer-rights/en.json +123 -0
- package/build/i18n/consumer-rights/es.json +123 -0
- package/build/i18n/consumer-rights/fr.json +123 -0
- package/build/i18n/consumer-rights/pl.json +123 -0
- package/build/i18n/consumer-rights/ru.json +123 -0
- package/build/i18n/consumer-rights/uk.json +123 -0
- package/build/i18n/errors/be.json +9 -1
- package/build/i18n/errors/de.json +9 -1
- package/build/i18n/errors/en.json +9 -1
- package/build/i18n/errors/es.json +9 -1
- package/build/i18n/errors/fr.json +9 -1
- package/build/i18n/errors/pl.json +9 -1
- package/build/i18n/errors/ru.json +9 -1
- package/build/i18n/errors/uk.json +9 -1
- package/build/i18n.js +23 -0
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/model/checkout.d.ts.map +1 -1
- package/build/model/checkout.js +3 -0
- package/build/model/checkout.js.map +1 -1
- package/build/model/consumer.d.ts +32 -0
- package/build/model/consumer.d.ts.map +1 -0
- package/build/model/consumer.js +425 -0
- package/build/model/consumer.js.map +1 -0
- package/build/model/estimate.d.ts.map +1 -1
- package/build/model/estimate.js +4 -2
- package/build/model/estimate.js.map +1 -1
- package/build/model/index.d.ts +1 -0
- package/build/model/index.d.ts.map +1 -1
- package/build/model/index.js +1 -0
- package/build/model/index.js.map +1 -1
- package/build/model/plan.d.ts.map +1 -1
- package/build/model/plan.js +10 -0
- package/build/model/plan.js.map +1 -1
- package/build/regions.d.ts +49 -0
- package/build/regions.d.ts.map +1 -0
- package/build/regions.js +85 -0
- package/build/regions.js.map +1 -0
- package/build/service.d.ts.map +1 -1
- package/build/service.js +9 -1
- package/build/service.js.map +1 -1
- package/build/types.d.ts +327 -3
- package/build/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/advertise.ts +7 -3
- package/src/checkout/index.ts +2 -0
- package/src/checkout/narrow.ts +72 -0
- package/src/checkout/protocols.ts +60 -0
- package/src/consts.ts +140 -0
- package/src/consumer/cancel.ts +41 -0
- package/src/consumer/copy.ts +126 -0
- package/src/consumer/deadline.ts +82 -0
- package/src/consumer/fifo.ts +71 -0
- package/src/consumer/index.ts +9 -0
- package/src/consumer/policy.ts +131 -0
- package/src/consumer/protocols.ts +191 -0
- package/src/consumer/refund.ts +191 -0
- package/src/consumer/refusal.ts +41 -0
- package/src/consumer/revive.ts +59 -0
- package/src/errors.ts +302 -0
- package/src/i18n/consumer-rights/be.json +123 -0
- package/src/i18n/consumer-rights/de.json +123 -0
- package/src/i18n/consumer-rights/en.json +123 -0
- package/src/i18n/consumer-rights/es.json +123 -0
- package/src/i18n/consumer-rights/fr.json +123 -0
- package/src/i18n/consumer-rights/pl.json +123 -0
- package/src/i18n/consumer-rights/ru.json +123 -0
- package/src/i18n/consumer-rights/uk.json +123 -0
- package/src/i18n/errors/be.json +9 -1
- package/src/i18n/errors/de.json +9 -1
- package/src/i18n/errors/en.json +9 -1
- package/src/i18n/errors/es.json +9 -1
- package/src/i18n/errors/fr.json +9 -1
- package/src/i18n/errors/pl.json +9 -1
- package/src/i18n/errors/ru.json +9 -1
- package/src/i18n/errors/uk.json +9 -1
- package/src/i18n.ts +25 -0
- package/src/index.ts +3 -0
- package/src/model/checkout.ts +3 -0
- package/src/model/consumer.ts +469 -0
- package/src/model/estimate.ts +4 -2
- package/src/model/index.ts +1 -0
- package/src/model/plan.ts +10 -0
- package/src/regions.ts +115 -0
- package/src/service.ts +14 -2
- package/src/types.ts +358 -4
- package/tests/checkout-narrow.spec.ts +75 -0
- package/tests/consumer-copy.spec.ts +193 -0
- package/tests/consumer-deadline.spec.ts +86 -0
- package/tests/consumer-fifo.spec.ts +61 -0
- package/tests/consumer-protocols.spec.ts +103 -0
- package/tests/consumer-refund.spec.ts +136 -0
- package/tests/consumer-regions.spec.ts +140 -0
- package/tests/consumer-wire.spec.ts +128 -0
- package/tests/errors.spec.ts +75 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ server integration (`@owlmeans/server-payment`) implements against these contrac
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
bun add @owlmeans/payment@^0.1.18-rc.
|
|
11
|
+
bun add @owlmeans/payment@^0.1.18-rc.38
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Concepts
|
|
@@ -129,7 +129,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
129
129
|
your project's skill store (`.agents/skills/`):
|
|
130
130
|
|
|
131
131
|
```sh
|
|
132
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
132
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.38
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
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/payment",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.38",
|
|
5
|
+
"generatedAt": "2026-09-23T19:57:40.693Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -141,6 +141,10 @@ reads the view through the pure selectors rather than polling it a second time.
|
|
|
141
141
|
and rebuild them on unmarshal. Catch the class after `ResilientError.ensure`.
|
|
142
142
|
- A UI phrases a refusal from `errors.<type>` and its fields (the reset date of an exhausted
|
|
143
143
|
window), never from the message text.
|
|
144
|
+
- Not entitlement refusals: the consumer-rights refusals (`PerformanceConsentRequired`,
|
|
145
|
+
`SubscriptionStartRequired` 428; `BillingCountryLocked`, `WithdrawalUnavailable`,
|
|
146
|
+
`CancellationUnavailable` 409) and `CheckoutLimitExceeded` (409) declare their status and never
|
|
147
|
+
extend `AuthForbidden` — the `payment` skill, § Consumer rights.
|
|
144
148
|
|
|
145
149
|
## Related
|
|
146
150
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: payment
|
|
3
|
-
description: "How to use @owlmeans/payment — provider-agnostic payment contracts, immutable payment protocols, amount/quantity checkout policies and pricing, catalogue records, and
|
|
3
|
+
description: "How to use @owlmeans/payment — provider-agnostic payment contracts, immutable payment protocols, amount/quantity checkout policies, per-entity checkout narrowing and pricing, catalogue records, entitlement gates, and the EU consumer-rights contracts (billing regions, the policy record, withdrawal/cancellation views, refund calculators, 428/409 refusals, the legal copy, protocol factories). Auto-invoked when importing payment types or errors, declaring paid routes, creating checkout, or touching consent, withdrawal or cancellation."
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,11 +8,13 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/payment
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/payment": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/payment": "^0.1.18-rc.38"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The contracts half of payments: the catalogue (products, plans, localizations), amount- and
|
|
14
|
-
quantity-priced checkout,
|
|
15
|
-
and the entitlement view the server gate and the browser both read
|
|
14
|
+
quantity-priced checkout, the entitlement model — plan capabilities, counted limits, promos,
|
|
15
|
+
and the entitlement view the server gate and the browser both read — and the EU consumer-rights
|
|
16
|
+
contracts (billing regions, the policy record, withdrawal and cancellation views, the refund
|
|
17
|
+
calculators, the refusals, the legal copy, the protocol factories). It talks to no paygate: a
|
|
16
18
|
server-side integration (`@owlmeans/server-payment`) implements against these, and
|
|
17
19
|
`@owlmeans/client-payment` adapts the catalogue service for a browser. The model as a whole —
|
|
18
20
|
admission, the usage ledger, the two gate services — is the `entitlements` skill.
|
|
@@ -22,8 +24,8 @@ admission, the usage ledger, the two gate services — is the `entitlements` ski
|
|
|
22
24
|
| Export | Description |
|
|
23
25
|
|---|---|
|
|
24
26
|
| `makePaymentService(alias?)` · `appendPaymentService(ctx, alias?)` | The catalogue reader, registered under `DEFAULT_ALIAS` / `PAYMENT_SERVICE` (`'payment'`). |
|
|
25
|
-
| `PaymentService` | `product(sku)` · `products()` · `plans(productSku, duration)` · `plan(planSku)` · `allPlans(productSku)` · `localize(lng, entity)` · `shallowAuthentication(token)
|
|
26
|
-
| `Product` · `ProductPlan` · `Localization` (+ schemas) | Catalogue records. `ProductPlan` carries `rank`, `free`, `gateways`, `suspendedAt`, `capabilities: PlanCapability[]`, `limits: { [key]: LimitDeclaration }
|
|
27
|
+
| `PaymentService` | `product(sku)` · `products()` · `plans(productSku, duration)` · `plan(planSku)` · `allPlans(productSku)` · `localize(lng, entity)` · `shallowAuthentication(token)` · `pricingPolicy()` · `consumerRightsPolicy()` (`null` when undeclared). |
|
|
28
|
+
| `Product` · `ProductPlan` · `Localization` (+ schemas) | Catalogue records. `ProductPlan` carries `rank`, `free`, `gateways`, `suspendedAt`, `capabilities: PlanCapability[]`, `limits: { [key]: LimitDeclaration }`, `withdrawal.components` (§ Consumer rights). |
|
|
27
29
|
| `PlanCapability` · `LimitDeclaration` · `PromoDeclaration` (+ schemas) | What a plan grants: a permission set, a counted allowance, a time box on either. |
|
|
28
30
|
| `SubscriptionStatus` · `ENTITLING_STATUSES` · `TERMINAL_STATUSES` · `INTERNAL_PAYGATE` | Lifecycle vocabulary. `Active`/`Trial`/`PastDue` entitle; `Canceled`/`Expired`/`Ended`/`Blocked` are terminal; `Suspended` is revoked until resumed. |
|
|
29
31
|
| `LimitKind` · `LimitWindow` · `PortalFlow` (+ schemas) | `window`/`lifetime`/`occupancy`; `day`/`month`; `manage`/`cancel`/`update`/`change`/`payment-method`. |
|
|
@@ -33,13 +35,22 @@ admission, the usage ledger, the two gate services — is the `entitlements` ski
|
|
|
33
35
|
| `promoActive` · `promoViewOf` | Whether a promo is in force for one subscription at one instant. |
|
|
34
36
|
| `EntitlementView` · `EntitlementPlanView` · `CapabilityView` · `LimitView` · `PromoView` · `LimitUsage` (+ wire schemas) | The entitlement view and its rows. |
|
|
35
37
|
| `capabilityViewsOf` · `limitViewsOf` · `entitlementViewOf` · `reviveEntitlementView` · `capabilityOf` · `limitOf` · `hasLimitRoom` | Pure view builders and readers. |
|
|
36
|
-
| `CreateCheckoutBody` (`planSku`) · `CreateCheckoutResponse` · `PortalLinkBody` · `PortalLinkResponse` (+ schemas) | Wire shapes an application's own checkout and portal protocols use. |
|
|
38
|
+
| `CreateCheckoutBody` (`planSku`, `country`, `startRequestId`) · `CreateCheckoutResponse` · `PortalLinkBody` · `PortalLinkResponse` (+ schemas) | Wire shapes an application's own checkout and portal protocols use. |
|
|
37
39
|
| `CheckoutPricingMode` · `AmountCheckoutPolicy` / `QuantityCheckoutPolicy` (+ schemas) · `assertAmountCheckoutPolicy` · `assertQuantityCheckoutPolicy` · `assertCheckoutAmount` · `chargeAmountMinor` | Checkout pricing policies and their validation. |
|
|
38
40
|
| `PricingPolicy` (+ schema) · `DEFAULT_PRICING_POLICY` · `assertPricingPolicy` · `TaxBehavior` · `TaxEstimateStatus` · `TaxType` (+ schemas) | Declared tax/currency behaviour (see § Pricing policy and tax estimate). |
|
|
39
41
|
| `PriceEstimate` · `PriceEstimateBody` · `TaxEstimate` · `TaxRateEstimate` (+ schemas) · `estimateOf` · `ratePpmOf` | The tax/local-currency estimate read model and its pure math. |
|
|
40
42
|
| `COUNTRY_CURRENCIES` · `currencyOfCountry` · `COUNTRY_CODES` · `CountrySchema` | ISO 3166-1 → ISO 4217 reference map for a country picker. |
|
|
41
|
-
| `EntitlementRefusal` · `CapabilityRequired` · `LimitExhausted` |
|
|
42
|
-
| `PaymentError` · `PaygateError` · `UnknownPaygate` · `PaygateMappingError` · `WebhookSetupError` · `PortalUnavailable` · `ProductError` · `UnknownProduct` · `UnknownPlan` · `PlanRequired` · `PlanRankConflict` · `LimitUnknown` · `LimitMisdeclared` · `PaymentIdentificationError` · `SubscriptionError` · `UnknownSubscription` | Faults (500), except `PortalUnavailable` (409). Importing the package registers every type's message under `errors.<type>` in the seven
|
|
43
|
+
| `EntitlementRefusal` · `CapabilityRequired` · `LimitExhausted` | Entitlement refusals — all `AuthForbidden` (403). |
|
|
44
|
+
| `PaymentError` · `PaygateError` · `UnknownPaygate` · `PaygateMappingError` · `WebhookSetupError` · `PortalUnavailable` · `ProductError` · `UnknownProduct` · `UnknownPlan` · `PlanRequired` · `PlanRankConflict` · `LimitUnknown` · `LimitMisdeclared` · `PaymentIdentificationError` · `SubscriptionError` · `UnknownSubscription` · `ConsumerRightsError` | Faults (500), except `PortalUnavailable` (409). Importing the package registers every type's message under `errors.<type>` in eight languages (the seven of `SUPPORTED_LNGS` plus `fr`). |
|
|
45
|
+
| `ConsumerRightsRefusal` · `PerformanceConsentRequired` · `SubscriptionStartRequired` (428) · `BillingCountryLocked` · `WithdrawalUnavailable` · `CancellationUnavailable` · `CheckoutLimitExceeded` (409) · `consentRefusalOf` | Consumer-rights and checkout-limit refusals — declared statuses, never `AuthForbidden` (§ Consumer rights). |
|
|
46
|
+
| `ConsumerRegion` · `PurchaseKind` · `ConsentKind` · `DeclarationKind` · `DeclarationChannel` · `CancellationKind` · `WithdrawalStatus` · `CancellationStatus` · `WithdrawalUnavailableReason` · `CancellationUnavailableReason` (+ schemas) | Consumer-rights vocabulary. |
|
|
47
|
+
| `EU_COUNTRIES` · `EU_CONSUMER_TERRITORIES` · `EEA_EXTRA` · `CONSUMER_RIGHTS_TERRITORIES` · `COUNTRY_LANGUAGES` · `isEuCountry` · `isEeaCountry` · `regionOf` · `inScope` · `chargeCurrencyOf` · `billingLanguageOf` | Territories, region, scope, charge currency, legal language. |
|
|
48
|
+
| `ConsumerRightsPolicy` (+ `Links`, `Mechanisms`, schemas) · `DEFAULT_CONSUMER_RIGHTS` · `makeConsumerRightsPolicy` · `assertConsumerRightsPolicy` · `linksOf` · `CONSUMER_RIGHTS_RECORD_TYPE`/`_ID` | The consumer-rights policy record. |
|
|
49
|
+
| `BillingProfileView` · `PurchaseView`/`List` · `PerformanceConsentView`/`Body`/`Response` · `SubscriptionStartView`/`Query`/`Body`/`Response` · `WithdrawalEstimate` · `WithdrawalCandidate`/`List` · `WithdrawalBody` · `DeclarationReceipt` · `WithdrawalReceipt` · `CancellationBody` · `CancellationReceipt` · `ConsumerRightsPublicView` (+ schemas) · `revive*` | Consumer-rights wire shapes (ISO dates) and their revivers. |
|
|
50
|
+
| `withdrawalDeadlineOf` · `lastWithdrawalDayOf` · `withdrawalOpen` · `oneTimeWithdrawalRefund` · `subscriptionWithdrawalRefund` · `splitByShares` · `allocateFifo` · `unitsUsedAfter` · `cancellationEffectiveAt` | Pure calculators. |
|
|
51
|
+
| `CONSUMER_RIGHTS_RESOURCE` · `CONSUMER_RIGHTS_COPY_VERSION` · `consumerRightsCopy` · `consumerText` · `consentStatementOf` · `legalLabelsOf` · `placeholdersOf` | The legal copy. |
|
|
52
|
+
| `makeConsumerRightsProtocols` · `makeCheckoutReadProtocols` | Protocol factories. |
|
|
53
|
+
| `AmountNarrowing` · `narrowAmountPolicy` · `amountAllowed` · `CheckoutLimitView` · `AmountPolicyView`/`Query` · `PlanPriceView`/`List`/`PlanPricesQuery` (+ schemas) | Per-entity narrowing of an amount checkout, and synced plan prices. |
|
|
43
54
|
|
|
44
55
|
Subpath: `./utils` — the `Config` / `Context` aliases to type your own context against.
|
|
45
56
|
|
|
@@ -172,6 +183,8 @@ A **limit** is counted: a `LimitDeclaration` under a key in `plan.limits`, asked
|
|
|
172
183
|
- **Only `type` and `message` survive a marshal.** Both refusals pack their fields into the
|
|
173
184
|
message and rebuild them in `finalizeUnmarshal()`; catch the class after
|
|
174
185
|
`ResilientError.ensure`, never parse the text yourself.
|
|
186
|
+
- **Consumer-rights and checkout-limit refusals are not entitlement refusals**: they declare
|
|
187
|
+
428/409 and never extend `AuthForbidden` (§ Consumer rights).
|
|
175
188
|
- **Faults are not refusals**: `LimitUnknown` (a key no plan declares), `LimitMisdeclared`,
|
|
176
189
|
`PlanRequired` (no plan resolvable, not even a free one), `PlanRankConflict`,
|
|
177
190
|
`WebhookSetupError` are `PaymentError`s — a configuration or setup problem, not the user's plan
|
|
@@ -188,9 +201,12 @@ A **limit** is counted: a `LimitDeclaration` under a key in `plan.limits`, asked
|
|
|
188
201
|
`CreateCheckoutBody` is the wire body of a checkout: `productSku`, `planSku` for a subscription,
|
|
189
202
|
`entitySlug` (the only organization value on a public body — a server resolves the stable
|
|
190
203
|
`entityId` before persisting anything), `service`, `amountMinor` for an amount checkout, return
|
|
191
|
-
URLs
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
URLs, `country` (the declared billing country; a locked profile overrides it) and
|
|
205
|
+
`startRequestId` (the subscription start request recorded just before). The package declares no
|
|
206
|
+
fixed checkout protocols: an application declares its own checkout and portal protocols over
|
|
207
|
+
`CreateCheckoutBodySchema` / `PortalLinkBodySchema`; `PortalLinkBody.flow` picks the portal flow
|
|
208
|
+
and `planSku` names the target of a `change`. The read side and the consumer-rights surface come
|
|
209
|
+
as factories (§ Protocol factories).
|
|
194
210
|
|
|
195
211
|
### Amount checkout
|
|
196
212
|
|
|
@@ -209,6 +225,29 @@ to `amountMinor`, not the adjusted checkout subtotal or tax-inclusive total.
|
|
|
209
225
|
Quantity checkout remains supported. It uses its reusable unit price and quantity policy; do not
|
|
210
226
|
infer a pricing mode from the presence of `amountMinor`.
|
|
211
227
|
|
|
228
|
+
### Narrowing an amount checkout per entity
|
|
229
|
+
|
|
230
|
+
The plan's `amountPolicy` is the same for everyone; what one entity may buy NOW is narrower when a
|
|
231
|
+
checkout plugin (a spending tier, a rolling cap, a fraud hold) answers an `AmountNarrowing`
|
|
232
|
+
`{ maximumMinor, reason, resetsAt?, remainingMinor? }`. `narrowAmountPolicy(base, narrowings,
|
|
233
|
+
{ productSku?, planSku? })` is the one computation the server's refusal and the dialog's control
|
|
234
|
+
share, so they cannot disagree:
|
|
235
|
+
|
|
236
|
+
- the maximum is the smallest of the base maximum and every narrowing's (negative or fractional
|
|
237
|
+
values floor at 0); `reason`/`resetsAt`/`remainingMinor` come from the narrowing that set it,
|
|
238
|
+
the first on a tie;
|
|
239
|
+
- presets above the maximum are dropped, the default is clamped into `[minimum, maximum]`;
|
|
240
|
+
- `limit` is `null` when nothing lowered the base maximum — no note to show;
|
|
241
|
+
- **`limit.blocked`** (`limit.maximumMinor < base.minimumMinor`) means no amount may be bought now.
|
|
242
|
+
The returned `policy` is then still a VALID policy (`assertAmountCheckoutPolicy` accepts it),
|
|
243
|
+
pinned to the minimum with no presets, so a validator never throws — but it is NOT an offer of
|
|
244
|
+
the minimum: a UI disables the input and the confirm button on `blocked`, and a server refuses
|
|
245
|
+
every amount with `CheckoutLimitExceeded`. `amountAllowed(view, amount)` answers exactly that.
|
|
246
|
+
|
|
247
|
+
`CheckoutLimitExceeded` (409) packs `checkout-limit-exceeded:<encodeURIComponent(reason)>:
|
|
248
|
+
<maximumMinor>:<currency>[:<resetsAt ISO>]` and rebuilds `reason`, `maximumMinor`, `currency`,
|
|
249
|
+
`resetsAt`. It is a payment refusal, not an entitlement one.
|
|
250
|
+
|
|
212
251
|
## Pricing policy and tax estimate
|
|
213
252
|
|
|
214
253
|
`PricingPolicy` (a `PRICING_POLICY_RECORD_ID` singleton config record, `declarePaymentPricing` in
|
|
@@ -223,7 +262,9 @@ or `DEFAULT_PRICING_POLICY` — the fixed behaviour every checkout had before th
|
|
|
223
262
|
an application that declares nothing sees no change.
|
|
224
263
|
|
|
225
264
|
`PriceEstimate` (built by `@owlmeans/server-payment`'s gateway, `estimatePrice`) is the read model:
|
|
226
|
-
`country`/`source` (`'request'`/`'customer'
|
|
265
|
+
`country`/`source` (`'request'`/`'customer'`, or `'profile'` with `locked: true` when the entity's
|
|
266
|
+
billing country is locked and overrides the request — a picker then shows it and does not change
|
|
267
|
+
it), `region`, `currency`, `behavior`, `tax: TaxEstimate` (`status`
|
|
227
268
|
one of `TaxEstimateStatus` — `taxed`/`reverse-charge`/`none`/`at-checkout`/`location-required` —
|
|
228
269
|
plus `subtotalMinor`/`taxMinor`/`totalMinor`, `scalable`, and `rates: TaxRateEstimate[]` with
|
|
229
270
|
`ratePpm` parsed by `ratePpmOf` from Stripe's `percentage_decimal`, never `Number(x) * 10_000`),
|
|
@@ -238,6 +279,169 @@ side by side. `COUNTRY_CURRENCIES` / `currencyOfCountry` / `COUNTRY_CODES` is a
|
|
|
238
279
|
ISO 4217 map for a country picker and the local-currency lookup — not a Stripe list, and a country
|
|
239
280
|
absent from it still gets a tax estimate, just no local-currency line.
|
|
240
281
|
|
|
282
|
+
## Consumer rights (EU withdrawal, cancellation, country lock)
|
|
283
|
+
|
|
284
|
+
The contracts behind the right of withdrawal (CRD Art. 9–16, the Art. 11a withdrawal function),
|
|
285
|
+
the cancellation function (§ 312k BGB / L215-1-1) and a billing country fixed at the first
|
|
286
|
+
purchase. `@owlmeans/server-payment` records, enforces and mails; `@owlmeans/web-payment` renders.
|
|
287
|
+
Nothing here is product copy — the trader, plan and product names are placeholders.
|
|
288
|
+
|
|
289
|
+
### Territories, region, scope
|
|
290
|
+
|
|
291
|
+
- `EU_COUNTRIES` (27, Greece is `GR`), `EU_CONSUMER_TERRITORIES` (plus AX, GF, GP, MQ, RE, YT, MF —
|
|
292
|
+
consumer law applies there even where EU VAT does not), `EEA_EXTRA` (IS, LI, NO) and their union
|
|
293
|
+
`CONSUMER_RIGHTS_TERRITORIES`, the default `policy.countries`.
|
|
294
|
+
- `regionOf(country, policy?)`: `Eu` inside the policy's territories, `Other` outside, `null` for no
|
|
295
|
+
country. `inScope(region, country, policy)`: a known country decides by the territories, else a
|
|
296
|
+
known region, else `unknownCountry` — `'protect'` by default, so an unknown buyer is protected.
|
|
297
|
+
- `chargeCurrencyOf(region, policy, fallback)`: the policy's currency for the region; an unknown
|
|
298
|
+
region reads as `Eu`. The display currency is the charge currency, everywhere.
|
|
299
|
+
- `billingLanguageOf(country, policy?, fallback?)`: `policy.languages`, then `COUNTRY_LANGUAGES`
|
|
300
|
+
(unambiguous countries only — BE, LU, CH are absent), then `fallback`, then
|
|
301
|
+
`policy.defaultLanguage`. Legal copy is shown in this language, with a toggle to the UI language.
|
|
302
|
+
|
|
303
|
+
### The policy record
|
|
304
|
+
|
|
305
|
+
`ConsumerRightsPolicy` is a singleton config record (`CONSUMER_RIGHTS_RECORD_ID`), declared by the
|
|
306
|
+
server integration through `makeConsumerRightsPolicy(def)` — `DEFAULT_CONSUMER_RIGHTS` filled in
|
|
307
|
+
(14 days, weekend rollover, margin 5 — a period ending on a public holiday runs to the next working
|
|
308
|
+
day and holiday clusters need up to five, no per-country calendar is kept —, every mechanism OFF,
|
|
309
|
+
`defaultLanguage: 'en'`, start requests
|
|
310
|
+
usable 3600 s) — and `assertConsumerRightsPolicy` (non-empty `textVersion`, `^[A-Z]{2}$`
|
|
311
|
+
countries, `withdrawalDays >= 14`, margin 0..7, lowercase currencies, https links, the default
|
|
312
|
+
language present, `withdrawalInformation` required while `withdrawal` or `performanceConsent` is
|
|
313
|
+
on; throws `ConsumerRightsError('policy:<field>')`). It carries only public links, territories
|
|
314
|
+
and switches — mail options live in a backend-only plugin config — so it is ADVERTISED to the
|
|
315
|
+
browser like the pricing policy. `PaymentService.consumerRightsPolicy()` answers `null` when none
|
|
316
|
+
is declared: no consumer-rights behaviour at all. `linksOf(policy, lng)` merges a language's links
|
|
317
|
+
field by field over the default language's (`de-AT` reads `de`).
|
|
318
|
+
|
|
319
|
+
`ProductPlan.withdrawal.components: PlanWithdrawalComponent[]` (`{ key, basis: 'time' | 'units',
|
|
320
|
+
shareMinor }`) states the separately priced parts of a subscription (CJEU C-641/19 PE Digital:
|
|
321
|
+
without them the whole price is pro rata by time).
|
|
322
|
+
|
|
323
|
+
### Wire views
|
|
324
|
+
|
|
325
|
+
Every view schema carries dates as ISO strings (as `model/view.ts` does) and is revived with its
|
|
326
|
+
`revive*` helper (`reviveConsentView`, `revivePurchase`/`List`, `reviveBillingProfile`,
|
|
327
|
+
`reviveConsentResponse`, `reviveStartResponse`, `reviveWithdrawalList`, `reviveReceipt`,
|
|
328
|
+
`reviveCheckoutLimit`, `reviveAmountPolicyView`) — idempotent. A nullable enum on the wire lists
|
|
329
|
+
`null` in its enum (ajv rejects `null` otherwise). The consent and start views carry `trader`, the
|
|
330
|
+
name the statement is rendered with, so the server's record and the dialog's text are identical.
|
|
331
|
+
`WithdrawalBody` and `CancellationBody` ask only for name, contract and e-mail (plus the
|
|
332
|
+
cancellation kind, reason and date) and accept an optional `honeypot` a person never fills. A
|
|
333
|
+
public declaration answers `DeclarationReceipt` — what was declared and when, never whether a
|
|
334
|
+
contract matched.
|
|
335
|
+
|
|
336
|
+
### Calculators — always in the consumer's favour
|
|
337
|
+
|
|
338
|
+
All amounts in BigInt; a deduction rounds DOWN, a refund rounds UP, a refund never exceeds what is
|
|
339
|
+
still unrefunded.
|
|
340
|
+
|
|
341
|
+
- `withdrawalDeadlineOf(purchasedAt, rule | policy)` → the EXCLUSIVE end: the purchase's UTC day +
|
|
342
|
+
`days`, a Saturday/Sunday last day moved to Monday, + `marginDays`, start of the next UTC day.
|
|
343
|
+
Wed 2026-09-23 → 2026-10-13T00:00Z; Sat 2026-09-26 → 2026-10-18T00:00Z; Sun 2026-12-20 →
|
|
344
|
+
2027-01-10T00:00Z (margin 5). `withdrawalOpen(window, at)` — before the deadline, not withdrawn,
|
|
345
|
+
not refunded. **A person is shown the last included day**, `lastWithdrawalDayOf(deadline)` (the
|
|
346
|
+
UTC day of `deadline − 1 ms`), phrased "until the end of <date>" — never the exclusive instant.
|
|
347
|
+
- `oneTimeWithdrawalRefund({ paidMinor, refundedMinor?, unitsGranted, unitsUsed })` →
|
|
348
|
+
`min(paid − refunded, ceil(paid × (granted − used) / granted))`; `unitsUsed` is the deduction —
|
|
349
|
+
units used AFTER consent (none before it) plus debt settled from the lot plus units already
|
|
350
|
+
clawed back (`@owlmeans/server-payment` passes the meter's `usedAfter + settled + clawed`);
|
|
351
|
+
returns `unitsReturned`. 1256 paid, 125k of 500k used → 942.
|
|
352
|
+
- `subscriptionWithdrawalRefund(...)` splits `netMinor` by the components (`splitByShares`, largest
|
|
353
|
+
remainder); a `time` part loses `floor(c × elapsedDays / periodDays)` counted from
|
|
354
|
+
`max(periodStart, servicesRequestedAt)` — NOTHING without a start request; a `units` part loses
|
|
355
|
+
`floor(c × used / granted)`; gross = `ceil(paid × refundNet / net)`. Net 2000 / paid 2460, 3 of 30
|
|
356
|
+
days and 100k of 500k used → 2091.
|
|
357
|
+
- `allocateFifo(lots, spends)` — a spend takes the oldest lot granted at or before it; overflow is
|
|
358
|
+
`unallocated`. `unitsUsedAfter(lot, consentedAt)` counts slices strictly after the consent, `0`
|
|
359
|
+
without one.
|
|
360
|
+
- `cancellationEffectiveAt(periodEnd, 'month' | 'year', requested?)` — the period end, or the first
|
|
361
|
+
boundary on or after a later requested date (end-of-month clamped from the anchor).
|
|
362
|
+
|
|
363
|
+
### Refusals
|
|
364
|
+
|
|
365
|
+
`ConsumerRightsRefusal` extends `ConsumerRightsError` (a `PaymentError`), NEVER `AuthForbidden`:
|
|
366
|
+
an HTTP boundary tests that family first, and a 403 would hide the status a client acts on. Each
|
|
367
|
+
refusal declares `static httpStatus`, packs its fields into the message and rebuilds them in
|
|
368
|
+
`finalizeUnmarshal()` (the marker is read after its LAST occurrence, so a registry rebuild of a
|
|
369
|
+
doubled prefix still parses):
|
|
370
|
+
|
|
371
|
+
| Class | Status | Marker (after `payment:consumer-rights:`) | Fields |
|
|
372
|
+
|---|---|---|---|
|
|
373
|
+
| `PerformanceConsentRequired` | 428 | `performance-consent-required:<pending>[:<deadline ISO>]` | `pending`, `deadline` |
|
|
374
|
+
| `SubscriptionStartRequired` | 428 | `subscription-start-required:<encodeURIComponent(planSku)>` | `planSku` (pass it raw) |
|
|
375
|
+
| `BillingCountryLocked` | 409 | `billing-country-locked:<country>[:<requested>]` | `country`, `requested` |
|
|
376
|
+
| `WithdrawalUnavailable` | 409 | `withdrawal-unavailable:<WithdrawalUnavailableReason>` | `reason` |
|
|
377
|
+
| `CancellationUnavailable` | 409 | `cancellation-unavailable:<CancellationUnavailableReason>` | `reason` |
|
|
378
|
+
|
|
379
|
+
`consentRefusalOf(e)` → `'performance' | 'subscription-start' | null`: the class after
|
|
380
|
+
`ResilientError.ensure`, else the marker or type name in `message`/`type`. A production body
|
|
381
|
+
carries only an incident id — a bare 428 is the caller's to read with `@owlmeans/api/status`.
|
|
382
|
+
|
|
383
|
+
### The legal copy
|
|
384
|
+
|
|
385
|
+
The i18n resource `payment-consumer-rights` (`CONSUMER_RIGHTS_RESOURCE`, library tier, `lib`
|
|
386
|
+
namespace), in en pl ru be uk es de fr, versioned by `CONSUMER_RIGHTS_COPY_VERSION` (bump it on ANY
|
|
387
|
+
change to a bundle). Branches: `performance-consent`, `subscription-start` (`title`, `intro`,
|
|
388
|
+
`request`, `acknowledgement`, `checkbox` = request + space + acknowledgement, `confirm`,
|
|
389
|
+
`decline`; the consent branch also `purchase`), `withdrawal`, `cancellation` (form labels and the
|
|
390
|
+
statutory buttons `function` / `confirm`), `links`, `checkout` (`terms-acceptance.{in-scope,
|
|
391
|
+
other}` markdown, `renewal.{month, year, after-submit}`, `price.{exclusive, inclusive, exclusive-tax}` (VAT wording for the territories, "applicable tax" for a buyer outside them), `top-up`,
|
|
392
|
+
`top-up-note`), `email.{common, consent, start, purchase, withdrawal, cancellation}` (the purchase
|
|
393
|
+
mail carries the CRD Annex I(A) withdrawal information with the function's address and the Annex
|
|
394
|
+
I(B) model form, per language from the national models; there `{{trader}}` is the trader's whole
|
|
395
|
+
identity — legal name, address, e-mail; a `review` withdrawal receipt promises the reimbursement
|
|
396
|
+
within the statutory 14 days; `email.cancellation.review` answers an extraordinary cancellation,
|
|
397
|
+
whose status is `CancellationStatus.Review`), `credit-note.memo`. In the consent and start
|
|
398
|
+
STATEMENTS `{{trader}}` is the trader's short name.
|
|
399
|
+
|
|
400
|
+
- Read it with `consumerRightsCopy(lng)` (any language, merged key by key over English, no i18next
|
|
401
|
+
instance, never drains a bundle — `resolveI18nResource`), `consumerText(lng, path, vars)` (fills
|
|
402
|
+
`{{name}}`; throws `ConsumerRightsError('copy:<path>[:<name>]')` on a missing text or value — a
|
|
403
|
+
legal text never goes out with a hole), `consentStatementOf(lng, kind, { trader, plan? })` (the
|
|
404
|
+
exact statement the dialog shows, the server records and the mail repeats) and
|
|
405
|
+
`legalLabelsOf(lng)`.
|
|
406
|
+
- Statutory labels are pinned by a test: en "Withdraw from contract here" / "Confirm withdrawal",
|
|
407
|
+
"Cancel contracts here" / "Cancel now"; de "Vertrag widerrufen" / "Widerruf bestätigen",
|
|
408
|
+
"Verträge hier kündigen" / "Jetzt kündigen"; fr "Renoncer au contrat ici" / "Confirmer la
|
|
409
|
+
rétractation", "Résilier votre contrat" / "Notification de la résiliation"; pl "Odstąp od umowy
|
|
410
|
+
tutaj" / "Potwierdź odstąpienie od umowy", "Wypowiedz umowę tutaj" / "Wypowiedz teraz"; es
|
|
411
|
+
"Desistir del contrato aquí" / "Confirmar el desistimiento", "Cancelar contratos aquí" /
|
|
412
|
+
"Cancelar ahora"; uk/ru/be are courtesy translations.
|
|
413
|
+
- The express request uses the statutory verbs (PL "Żądam i wyrażam wyraźną zgodę … Przyjmuję do
|
|
414
|
+
wiadomości …", DE "Ich verlange ausdrücklich und stimme ausdrücklich zu … Mir ist bekannt …", FR
|
|
415
|
+
"Je demande expressément et j’accepte expressément … Je reconnais perdre …").
|
|
416
|
+
- Wording rule, enforced by a scan of every language: say "the right of withdrawal expires" and
|
|
417
|
+
"only unused credits are reimbursed" — never non-refundable / nicht erstattungsfähig / non
|
|
418
|
+
remboursable / bezzwrotny / no reembolsable / невозвратный / неповоротний / незваротны.
|
|
419
|
+
- Paygate texts (`checkout.*`) stay within 1200 characters after interpolating long URLs.
|
|
420
|
+
- An application overrides a text with `addI18nApp(lng, CONSUMER_RIGHTS_RESOURCE, data, { ns:
|
|
421
|
+
LIB_NAMESPACE })`; every placeholder of a text must match its English master.
|
|
422
|
+
|
|
423
|
+
### Protocol factories
|
|
424
|
+
|
|
425
|
+
`makeConsumerRightsProtocols({ prefix, parent | guards (+ gate), path = '/consumer-rights',
|
|
426
|
+
public?: false | { path = '/public/consumer-rights', parent?, screens?: { withdrawal?,
|
|
427
|
+
cancellation?, parent? } } })`, the `makeMarketingConsentProtocols` way:
|
|
428
|
+
|
|
429
|
+
- account subtree under the app's GUARDED parent (its guards and gate inherited): `base`,
|
|
430
|
+
`profile` GET `/profile`, `purchases` GET `/purchases`, `consent` GET / `giveConsent` POST
|
|
431
|
+
`/consent`, `start` GET `/start?planSku` / `requestStart` POST `/start`, `withdrawals` GET /
|
|
432
|
+
`withdraw` POST `/withdrawal`, `cancel` POST `/cancellation`; aliases `<prefix>:<name>`
|
|
433
|
+
(`consent:give`, `start:request`);
|
|
434
|
+
- `public` (only when declared): `base`, `policy` GET `/policy`, `withdraw` POST `/withdrawal`,
|
|
435
|
+
`cancel` POST `/cancellation` — NO guard and NO gate (like `paymentGate.webhook`; a public
|
|
436
|
+
`parent` must be unguarded), receipts typed `DeclarationReceipt`; `screens` declares BOTH sticky
|
|
437
|
+
frontend routes (`/legal/withdraw`, `/legal/cancel` by default); aliases `<prefix>:public:<name>`;
|
|
438
|
+
- neither `parent` nor `guards` is a `SyntaxError`; absent parts are left OUT of the tree (never
|
|
439
|
+
`undefined`), so `protocols(tree)` and `mapProtocols` walk it as is.
|
|
440
|
+
|
|
441
|
+
`makeCheckoutReadProtocols({ prefix, parent | guards })` → `base` (`/checkout`), `amountPolicy`
|
|
442
|
+
GET `/amount-policy?productSku&planSku` (`AmountPolicyView`) and `planPrices` GET
|
|
443
|
+
`/plan-prices?productSku` (`PlanPriceList`).
|
|
444
|
+
|
|
241
445
|
## `shallowAuthentication` identifies, it does not authorize
|
|
242
446
|
|
|
243
447
|
It reads the `profileId` out of an envelope token WITHOUT verifying the signature, and throws
|
package/build/advertise.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { CONFIG_RECORD } from '@owlmeans/context';
|
|
2
2
|
import { apiConfigPlugin, every } from '@owlmeans/api-config';
|
|
3
|
-
import { L10N_RECORD_TYPE, PLAN_RECORD_TYPE, PRICING_POLICY_RECORD_TYPE, PRODUCT_RECORD_TYPE } from './consts.js';
|
|
3
|
+
import { CONSUMER_RIGHTS_RECORD_TYPE, L10N_RECORD_TYPE, PLAN_RECORD_TYPE, PRICING_POLICY_RECORD_TYPE, PRODUCT_RECORD_TYPE, } from './consts.js';
|
|
4
4
|
/**
|
|
5
5
|
* The pricing policy record carries only flags and TTLs (never a Stripe secret, an API version, or
|
|
6
6
|
* a migration switch — those stay in a backend-only plugin), so advertising it to the browser is
|
|
7
|
-
* safe.
|
|
7
|
+
* safe. The consumer-rights policy record carries only public links, territories and switches
|
|
8
|
+
* (the mail options — sender, archive copy — stay in a backend-only plugin config), so the browser
|
|
9
|
+
* renders the same links and switches the server enforces.
|
|
8
10
|
*/
|
|
9
11
|
const advertisedRecordTypes = new Set([
|
|
10
|
-
L10N_RECORD_TYPE, PLAN_RECORD_TYPE, PRODUCT_RECORD_TYPE, PRICING_POLICY_RECORD_TYPE,
|
|
12
|
+
L10N_RECORD_TYPE, PLAN_RECORD_TYPE, PRODUCT_RECORD_TYPE, PRICING_POLICY_RECORD_TYPE, CONSUMER_RIGHTS_RECORD_TYPE,
|
|
11
13
|
]);
|
|
12
14
|
apiConfigPlugin({
|
|
13
15
|
allow: {
|
package/build/advertise.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advertise.js","sourceRoot":"","sources":["../src/advertise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"advertise.js","sourceRoot":"","sources":["../src/advertise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EACL,2BAA2B,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,mBAAmB,GACjH,MAAM,aAAa,CAAA;AAEpB;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,2BAA2B;CACjH,CAAC,CAAA;AAEF,eAAe,CAAC;IACd,KAAK,EAAE;QACL,CAAC,aAAa,CAAC,EAAE,KAAK,CACpB,IAAI,EACJ,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;eAC9C,qBAAqB,CAAC,GAAG,CAAE,KAAiC,CAAC,UAAU,IAAI,EAAE,CAAC,CACpF;KACF;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AmountCheckoutPolicy, AmountNarrowing, AmountPolicyView } from '../types.js';
|
|
2
|
+
export interface NarrowAmountPolicyOptions {
|
|
3
|
+
productSku?: string;
|
|
4
|
+
planSku?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* An amount policy narrowed for one entity — the one computation the server's refusal and the
|
|
8
|
+
* dialog's control share, so a disabled control and a refusal cannot disagree.
|
|
9
|
+
*
|
|
10
|
+
* - The maximum is the smallest of the base maximum and every narrowing's; the reason, reset and
|
|
11
|
+
* remaining come from the narrowing that set it (the first on a tie).
|
|
12
|
+
* - Presets above the maximum are dropped; the default is clamped into `[minimum, maximum]`.
|
|
13
|
+
* - `limit` is `null` when no narrowing lowered the base maximum.
|
|
14
|
+
* - `blocked` (`limit.maximumMinor < base.minimumMinor`) means NO amount may be bought now. The
|
|
15
|
+
* returned `policy` then stays a VALID policy pinned to the minimum (maximum = default =
|
|
16
|
+
* minimum, no presets above it), so a validator never throws on it; the UI disables the input
|
|
17
|
+
* and the confirm button on `limit.blocked`, and the server refuses every amount with
|
|
18
|
+
* `CheckoutLimitExceeded` — neither may read the pinned policy as "the minimum is allowed".
|
|
19
|
+
*
|
|
20
|
+
* @throws PaymentError (`checkout-policy:*`) when the base policy itself is invalid.
|
|
21
|
+
*/
|
|
22
|
+
export declare const narrowAmountPolicy: (base: AmountCheckoutPolicy, narrowings: AmountNarrowing[], opts?: NarrowAmountPolicyOptions) => AmountPolicyView;
|
|
23
|
+
/** Whether an amount may be bought under a narrowed view: never when blocked. */
|
|
24
|
+
export declare const amountAllowed: (view: AmountPolicyView, amountMinor: number) => boolean;
|
|
25
|
+
//# sourceMappingURL=narrow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrow.d.ts","sourceRoot":"","sources":["../../src/checkout/narrow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAA;AAE7G,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,SACvB,oBAAoB,cAAc,eAAe,EAAE,SAAQ,yBAAyB,KACzF,gBAqCF,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,aAAa,SAAU,gBAAgB,eAAe,MAAM,KAAG,OAEW,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assertAmountCheckoutPolicy } from '../pricing.js';
|
|
2
|
+
const maximumOf = (narrowing) => Number.isFinite(narrowing.maximumMinor) ? Math.max(0, Math.floor(narrowing.maximumMinor)) : 0;
|
|
3
|
+
/**
|
|
4
|
+
* An amount policy narrowed for one entity — the one computation the server's refusal and the
|
|
5
|
+
* dialog's control share, so a disabled control and a refusal cannot disagree.
|
|
6
|
+
*
|
|
7
|
+
* - The maximum is the smallest of the base maximum and every narrowing's; the reason, reset and
|
|
8
|
+
* remaining come from the narrowing that set it (the first on a tie).
|
|
9
|
+
* - Presets above the maximum are dropped; the default is clamped into `[minimum, maximum]`.
|
|
10
|
+
* - `limit` is `null` when no narrowing lowered the base maximum.
|
|
11
|
+
* - `blocked` (`limit.maximumMinor < base.minimumMinor`) means NO amount may be bought now. The
|
|
12
|
+
* returned `policy` then stays a VALID policy pinned to the minimum (maximum = default =
|
|
13
|
+
* minimum, no presets above it), so a validator never throws on it; the UI disables the input
|
|
14
|
+
* and the confirm button on `limit.blocked`, and the server refuses every amount with
|
|
15
|
+
* `CheckoutLimitExceeded` — neither may read the pinned policy as "the minimum is allowed".
|
|
16
|
+
*
|
|
17
|
+
* @throws PaymentError (`checkout-policy:*`) when the base policy itself is invalid.
|
|
18
|
+
*/
|
|
19
|
+
export const narrowAmountPolicy = (base, narrowings, opts = {}) => {
|
|
20
|
+
assertAmountCheckoutPolicy(base);
|
|
21
|
+
let setter = null;
|
|
22
|
+
let maximum = base.maximumMinor;
|
|
23
|
+
for (const narrowing of narrowings) {
|
|
24
|
+
const candidate = maximumOf(narrowing);
|
|
25
|
+
if (candidate < maximum) {
|
|
26
|
+
maximum = candidate;
|
|
27
|
+
setter = narrowing;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (setter == null) {
|
|
31
|
+
return { policy: base, limit: null };
|
|
32
|
+
}
|
|
33
|
+
const blocked = maximum < base.minimumMinor;
|
|
34
|
+
const ceiling = blocked ? base.minimumMinor : maximum;
|
|
35
|
+
const policy = {
|
|
36
|
+
...base,
|
|
37
|
+
maximumMinor: ceiling,
|
|
38
|
+
defaultMinor: Math.min(Math.max(base.defaultMinor, base.minimumMinor), ceiling),
|
|
39
|
+
presetsMinor: base.presetsMinor.filter(preset => preset <= ceiling),
|
|
40
|
+
};
|
|
41
|
+
const limit = {
|
|
42
|
+
productSku: opts.productSku ?? '',
|
|
43
|
+
...(opts.planSku != null ? { planSku: opts.planSku } : {}),
|
|
44
|
+
currency: base.currency,
|
|
45
|
+
minimumMinor: base.minimumMinor,
|
|
46
|
+
maximumMinor: maximum,
|
|
47
|
+
narrowed: true,
|
|
48
|
+
blocked,
|
|
49
|
+
reason: setter.reason,
|
|
50
|
+
...(setter.resetsAt != null ? { resetsAt: setter.resetsAt } : {}),
|
|
51
|
+
...(setter.remainingMinor != null ? { remainingMinor: setter.remainingMinor } : {}),
|
|
52
|
+
};
|
|
53
|
+
return { policy: assertAmountCheckoutPolicy(policy), limit };
|
|
54
|
+
};
|
|
55
|
+
/** Whether an amount may be bought under a narrowed view: never when blocked. */
|
|
56
|
+
export const amountAllowed = (view, amountMinor) => view.limit?.blocked !== true && Number.isSafeInteger(amountMinor)
|
|
57
|
+
&& amountMinor >= view.policy.minimumMinor && amountMinor <= view.policy.maximumMinor;
|
|
58
|
+
//# sourceMappingURL=narrow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrow.js","sourceRoot":"","sources":["../../src/checkout/narrow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA;AAQ1D,MAAM,SAAS,GAAG,CAAC,SAA0B,EAAU,EAAE,CACvD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,IAA0B,EAAE,UAA6B,EAAE,IAAI,GAA8B,EAAE,EAC7E,EAAE;IACpB,0BAA0B,CAAC,IAAI,CAAC,CAAA;IAChC,IAAI,MAAM,GAA2B,IAAI,CAAA;IACzC,IAAI,OAAO,GAAG,IAAI,CAAC,YAAY,CAAA;IAC/B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,CAAA;QACtC,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC;YACxB,OAAO,GAAG,SAAS,CAAA;YACnB,MAAM,GAAG,SAAS,CAAA;QACpB,CAAC;IACH,CAAC;IACD,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACtC,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC,YAAY,CAAA;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAA;IACrD,MAAM,MAAM,GAAyB;QACnC,GAAG,IAAI;QACP,YAAY,EAAE,OAAO;QACrB,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAC/E,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,OAAO,CAAC;KACpE,CAAA;IACD,MAAM,KAAK,GAAsB;QAC/B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE;QACjC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,IAAI;QACd,OAAO;QACP,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpF,CAAA;IAED,OAAO,EAAE,MAAM,EAAE,0BAA0B,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;AAC9D,CAAC,CAAA;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAsB,EAAE,WAAmB,EAAW,EAAE,CACpF,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC;OAC9D,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EntrypointOptions, EntrypointProtocol, OpenRequest, OpenValue } from '@owlmeans/entrypoint';
|
|
2
|
+
import type { RouteParent } from '@owlmeans/route';
|
|
3
|
+
import type { AmountPolicyQuery, AmountPolicyView, PlanPriceList, PlanPricesQuery } from '../types.js';
|
|
4
|
+
export declare const CHECKOUT_READ_API_PATH = "/checkout";
|
|
5
|
+
export interface CheckoutReadProtocolOptions {
|
|
6
|
+
/** Alias prefix of every declaration, e.g. `my-app:account:checkout`. */
|
|
7
|
+
prefix: string;
|
|
8
|
+
/** The application's guarded parent — its guards and gate are inherited. */
|
|
9
|
+
parent?: RouteParent;
|
|
10
|
+
guards?: EntrypointOptions['guards'];
|
|
11
|
+
gate?: EntrypointOptions['gate'];
|
|
12
|
+
/** Default `/checkout`. */
|
|
13
|
+
path?: string;
|
|
14
|
+
}
|
|
15
|
+
export type CheckoutReadProtocols = {
|
|
16
|
+
base: EntrypointProtocol<OpenRequest, OpenValue>;
|
|
17
|
+
/** The entity's amount policy as narrowed now — the same computation the checkout enforces. */
|
|
18
|
+
amountPolicy: EntrypointProtocol<{
|
|
19
|
+
query: AmountPolicyQuery;
|
|
20
|
+
}, AmountPolicyView>;
|
|
21
|
+
/** The prices a product's plans are charged at, per currency, as last synchronized. */
|
|
22
|
+
planPrices: EntrypointProtocol<{
|
|
23
|
+
query: PlanPricesQuery;
|
|
24
|
+
}, PlanPriceList>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The read side of checkout: `amountPolicy` GET `/amount-policy?productSku&planSku` and
|
|
28
|
+
* `planPrices` GET `/plan-prices?productSku`, under the application's guarded `parent` (or its own
|
|
29
|
+
* `guards`). Aliases are `<prefix>:<name>`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const makeCheckoutReadProtocols: (opts: CheckoutReadProtocolOptions) => CheckoutReadProtocols;
|
|
32
|
+
//# sourceMappingURL=protocols.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocols.d.ts","sourceRoot":"","sources":["../../src/checkout/protocols.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEzG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAIlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEtG,eAAO,MAAM,sBAAsB,cAAc,CAAA;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAChC,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAChD,+FAA+F;IAC/F,YAAY,EAAE,kBAAkB,CAAC;QAAE,KAAK,EAAE,iBAAiB,CAAA;KAAE,EAAE,gBAAgB,CAAC,CAAA;IAChF,uFAAuF;IACvF,UAAU,EAAE,kBAAkB,CAAC;QAAE,KAAK,EAAE,eAAe,CAAA;KAAE,EAAE,aAAa,CAAC,CAAA;CAC1E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,SAAU,2BAA2B,KAAG,qBAwB7E,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint';
|
|
2
|
+
import { backend, route, RouteMethod } from '@owlmeans/route';
|
|
3
|
+
import { AmountPolicyQuerySchema, AmountPolicyViewSchema, PlanPriceListSchema, PlanPricesQuerySchema, } from '../model/consumer.js';
|
|
4
|
+
export const CHECKOUT_READ_API_PATH = '/checkout';
|
|
5
|
+
/**
|
|
6
|
+
* The read side of checkout: `amountPolicy` GET `/amount-policy?productSku&planSku` and
|
|
7
|
+
* `planPrices` GET `/plan-prices?productSku`, under the application's guarded `parent` (or its own
|
|
8
|
+
* `guards`). Aliases are `<prefix>:<name>`.
|
|
9
|
+
*/
|
|
10
|
+
export const makeCheckoutReadProtocols = (opts) => {
|
|
11
|
+
if (opts.parent == null && opts.guards == null) {
|
|
12
|
+
throw new SyntaxError('checkout-read: the routes need either a parent or explicit guards');
|
|
13
|
+
}
|
|
14
|
+
const alias = (name) => `${opts.prefix}:${name}`;
|
|
15
|
+
const path = opts.path ?? CHECKOUT_READ_API_PATH;
|
|
16
|
+
const base = opts.parent != null
|
|
17
|
+
? openProtocol(route(alias('base'), path, backend({ parent: opts.parent })))
|
|
18
|
+
: openProtocol(route(alias('base'), path, backend()), {
|
|
19
|
+
guards: opts.guards,
|
|
20
|
+
...(opts.gate != null ? { gate: opts.gate } : {}),
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
base,
|
|
24
|
+
amountPolicy: protocol(route(alias('amount-policy'), '/amount-policy', backend({ parent: base, method: RouteMethod.GET })), contract.request({ query: typed(AmountPolicyQuerySchema) }, AmountPolicyViewSchema)),
|
|
25
|
+
planPrices: protocol(route(alias('plan-prices'), '/plan-prices', backend({ parent: base, method: RouteMethod.GET })), contract.request({ query: typed(PlanPricesQuerySchema) }, PlanPriceListSchema)),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=protocols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocols.js","sourceRoot":"","sources":["../../src/checkout/protocols.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EACL,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,qBAAqB,GAC5F,MAAM,sBAAsB,CAAA;AAG7B,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAA;AAqBjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,IAAiC,EAAyB,EAAE;IACpG,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;QAC/C,MAAM,IAAI,WAAW,CAAC,mEAAmE,CAAC,CAAA;IAC5F,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAA;IAChE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,sBAAsB,CAAA;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI;QAC9B,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YAClD,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC,CAAA;IAEN,OAAO;QACL,IAAI;QACJ,YAAY,EAAE,QAAQ,CACpB,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,EACnG,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAoB,uBAAuB,CAAC,EAAE,EAAE,sBAAsB,CAAC,CACvG;QACD,UAAU,EAAE,QAAQ,CAClB,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,EAC/F,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAkB,qBAAqB,CAAC,EAAE,EAAE,mBAAmB,CAAC,CAChG;KACF,CAAA;AACH,CAAC,CAAA"}
|