@openrails/billing-ui 0.0.0-stage → 0.230.0

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +257 -0
  2. package/LICENSE +21 -0
  3. package/README.md +211 -2
  4. package/dist/account/account-billing.d.ts +19 -0
  5. package/dist/account/cancel-dialog.d.ts +18 -0
  6. package/dist/account/change-card-dialog.d.ts +14 -0
  7. package/dist/account/format.d.ts +25 -0
  8. package/dist/account/notice.d.ts +3 -0
  9. package/dist/account/payment-history.d.ts +8 -0
  10. package/dist/account/payment-methods-panel.d.ts +19 -0
  11. package/dist/account/section.d.ts +25 -0
  12. package/dist/account/status-badge.d.ts +8 -0
  13. package/dist/account/subscriptions-panel.d.ts +19 -0
  14. package/dist/appearance.d.ts +23 -0
  15. package/dist/authenticate.d.ts +8 -0
  16. package/dist/checkout-frame.d.ts +17 -0
  17. package/dist/checkout-page.d.ts +16 -0
  18. package/dist/checkout.d.ts +24 -0
  19. package/dist/client/client.d.ts +167 -0
  20. package/dist/client/errors.d.ts +26 -0
  21. package/dist/client/generated/openrails-routes.d.ts +2 -0
  22. package/dist/client/index.d.ts +4 -0
  23. package/dist/client/types.d.ts +754 -0
  24. package/dist/client-DhG2jv34.js +771 -0
  25. package/dist/client-DhG2jv34.js.map +1 -0
  26. package/dist/client.js +3 -0
  27. package/dist/components/billing-fields.d.ts +39 -0
  28. package/dist/components/card-brands.d.ts +6 -0
  29. package/dist/components/card-fields.d.ts +29 -0
  30. package/dist/components/ccbill-fields.d.ts +8 -0
  31. package/dist/components/methods.d.ts +13 -0
  32. package/dist/components/pay-button.d.ts +6 -0
  33. package/dist/components/qr.d.ts +6 -0
  34. package/dist/components/saved-methods.d.ts +9 -0
  35. package/dist/components/states.d.ts +10 -0
  36. package/dist/components/stripe-card.d.ts +17 -0
  37. package/dist/components/summary.d.ts +12 -0
  38. package/dist/components/ui/alert-dialog.d.ts +18 -0
  39. package/dist/components/ui/badge.d.ts +7 -0
  40. package/dist/components/ui/button.d.ts +8 -0
  41. package/dist/components/ui/card.d.ts +11 -0
  42. package/dist/components/ui/dialog.d.ts +17 -0
  43. package/dist/components/ui/input.d.ts +3 -0
  44. package/dist/components/ui/label.d.ts +3 -0
  45. package/dist/components/ui/radio-group.d.ts +5 -0
  46. package/dist/components/ui/separator.d.ts +3 -0
  47. package/dist/components/ui/skeleton.d.ts +2 -0
  48. package/dist/components/ui/spinner.d.ts +3 -0
  49. package/dist/components/ui/textarea.d.ts +3 -0
  50. package/dist/dist-CslWupfi.js +3311 -0
  51. package/dist/dist-CslWupfi.js.map +1 -0
  52. package/dist/errors-T-PRm3vV.js +54 -0
  53. package/dist/errors-T-PRm3vV.js.map +1 -0
  54. package/dist/fixtures.d.ts +9 -0
  55. package/dist/frame.d.ts +47 -0
  56. package/dist/hooks-DLFF-zx5.js +285 -0
  57. package/dist/hooks-DLFF-zx5.js.map +1 -0
  58. package/dist/i18n/context.d.ts +4 -0
  59. package/dist/i18n/index.d.ts +2 -0
  60. package/dist/i18n/messages.d.ts +58 -0
  61. package/dist/index.d.ts +29 -0
  62. package/dist/index.js +4182 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/lib/billing.d.ts +25 -0
  65. package/dist/lib/card-brands.d.ts +2 -0
  66. package/dist/lib/card-entry.d.ts +46 -0
  67. package/dist/lib/ccbill.d.ts +8 -0
  68. package/dist/lib/collect.d.ts +51 -0
  69. package/dist/lib/money.d.ts +9 -0
  70. package/dist/lib/period.d.ts +15 -0
  71. package/dist/lib/rail-meta.d.ts +15 -0
  72. package/dist/lib/redirect.d.ts +2 -0
  73. package/dist/lib/stripe-appearance.d.ts +2 -0
  74. package/dist/lib/stripe.d.ts +2 -0
  75. package/dist/locales/de.d.ts +2 -0
  76. package/dist/locales/de.js +204 -0
  77. package/dist/locales/de.js.map +1 -0
  78. package/dist/locales/en.d.ts +201 -0
  79. package/dist/locales/en.js +204 -0
  80. package/dist/locales/en.js.map +1 -0
  81. package/dist/locales/es.d.ts +2 -0
  82. package/dist/locales/es.js +204 -0
  83. package/dist/locales/es.js.map +1 -0
  84. package/dist/locales/ja.d.ts +2 -0
  85. package/dist/locales/ja.js +195 -0
  86. package/dist/locales/ja.js.map +1 -0
  87. package/dist/locales/ko.d.ts +2 -0
  88. package/dist/locales/ko.js +195 -0
  89. package/dist/locales/ko.js.map +1 -0
  90. package/dist/locales/zh.d.ts +2 -0
  91. package/dist/locales/zh.js +195 -0
  92. package/dist/locales/zh.js.map +1 -0
  93. package/dist/modal.d.ts +12 -0
  94. package/dist/provider.d.ts +18 -0
  95. package/dist/psp.d.ts +46 -0
  96. package/dist/react/context.d.ts +38 -0
  97. package/dist/react/hooks.d.ts +76 -0
  98. package/dist/react/index.d.ts +3 -0
  99. package/dist/react/provider.d.ts +18 -0
  100. package/dist/react/remote.d.ts +11 -0
  101. package/dist/react.js +37 -0
  102. package/dist/react.js.map +1 -0
  103. package/dist/save-payment-method.d.ts +26 -0
  104. package/dist/scope-context.d.ts +17 -0
  105. package/dist/scope.d.ts +6 -0
  106. package/dist/source.d.ts +14 -0
  107. package/dist/styles.css +3 -0
  108. package/dist/tokenized-card-form.d.ts +24 -0
  109. package/dist/types.d.ts +191 -0
  110. package/docs/1055-nmi-host-integration.md +28 -0
  111. package/docs/payment-form-contract.md +153 -0
  112. package/package.json +112 -3
@@ -0,0 +1,191 @@
1
+ import { z } from 'zod';
2
+ export declare const amountSchema: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
3
+ export declare const unitDecimalsSchema: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
4
+ export declare const checkoutSessionStatusSchema: z.ZodEnum<{
5
+ succeeded: "succeeded";
6
+ failed: "failed";
7
+ expired: "expired";
8
+ created: "created";
9
+ requires_action: "requires_action";
10
+ processing: "processing";
11
+ blocked: "blocked";
12
+ canceled: "canceled";
13
+ }>;
14
+ export type CheckoutSessionStatus = z.infer<typeof checkoutSessionStatusSchema>;
15
+ export declare const paymentFailureSchema: z.ZodObject<{
16
+ reason: z.ZodString;
17
+ message: z.ZodString;
18
+ field: z.ZodOptional<z.ZodNullable<z.ZodString>>;
19
+ }, z.core.$strip>;
20
+ export type PaymentFailure = z.infer<typeof paymentFailureSchema>;
21
+ export declare const checkoutOperationSchema: z.ZodObject<{
22
+ id: z.ZodString;
23
+ status: z.ZodString;
24
+ }, z.core.$strip>;
25
+ export declare const paymentRailOptionSchema: z.ZodObject<{
26
+ id: z.ZodString;
27
+ rail: z.ZodString;
28
+ mode: z.ZodEnum<{
29
+ subscription: "subscription";
30
+ one_off: "one_off";
31
+ }>;
32
+ driver: z.ZodEnum<{
33
+ card: "card";
34
+ collect_js: "collect_js";
35
+ stripe_elements: "stripe_elements";
36
+ redirect: "redirect";
37
+ solana_pay: "solana_pay";
38
+ }>;
39
+ psp_key: z.ZodOptional<z.ZodString>;
40
+ public_config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
41
+ }, z.core.$strip>;
42
+ export type PaymentRailOption = z.infer<typeof paymentRailOptionSchema>;
43
+ export declare const savedPaymentMethodSchema: z.ZodObject<{
44
+ id: z.ZodString;
45
+ option_id: z.ZodString;
46
+ rail: z.ZodString;
47
+ brand: z.ZodOptional<z.ZodString>;
48
+ last_four: z.ZodOptional<z.ZodString>;
49
+ exp_month: z.ZodOptional<z.ZodNumber>;
50
+ exp_year: z.ZodOptional<z.ZodNumber>;
51
+ default: z.ZodOptional<z.ZodBoolean>;
52
+ }, z.core.$strip>;
53
+ export type SavedPaymentMethod = z.infer<typeof savedPaymentMethodSchema>;
54
+ export declare const checkoutLineItemSchema: z.ZodObject<{
55
+ label: z.ZodString;
56
+ sublabel: z.ZodOptional<z.ZodString>;
57
+ amount: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
58
+ }, z.core.$strip>;
59
+ export type CheckoutLineItem = z.infer<typeof checkoutLineItemSchema>;
60
+ export declare const checkoutPlanSchema: z.ZodObject<{
61
+ display_name: z.ZodString;
62
+ unit_amount: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
63
+ currency: z.ZodString;
64
+ unit_decimals: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
65
+ period_hours: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
66
+ automatically_renews: z.ZodBoolean;
67
+ }, z.core.$strip>;
68
+ export type CheckoutPlan = z.infer<typeof checkoutPlanSchema>;
69
+ export declare const checkoutSessionSchema: z.ZodObject<{
70
+ id: z.ZodString;
71
+ status: z.ZodEnum<{
72
+ succeeded: "succeeded";
73
+ failed: "failed";
74
+ expired: "expired";
75
+ created: "created";
76
+ requires_action: "requires_action";
77
+ processing: "processing";
78
+ blocked: "blocked";
79
+ canceled: "canceled";
80
+ }>;
81
+ merchant: z.ZodObject<{
82
+ display_name: z.ZodString;
83
+ }, z.core.$strip>;
84
+ plan: z.ZodObject<{
85
+ display_name: z.ZodString;
86
+ unit_amount: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
87
+ currency: z.ZodString;
88
+ unit_decimals: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
89
+ period_hours: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
90
+ automatically_renews: z.ZodBoolean;
91
+ }, z.core.$strip>;
92
+ line_items: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
+ label: z.ZodString;
94
+ sublabel: z.ZodOptional<z.ZodString>;
95
+ amount: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
96
+ }, z.core.$strip>>>;
97
+ tax: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
98
+ due_today: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
99
+ rails: z.ZodArray<z.ZodObject<{
100
+ id: z.ZodString;
101
+ rail: z.ZodString;
102
+ mode: z.ZodEnum<{
103
+ subscription: "subscription";
104
+ one_off: "one_off";
105
+ }>;
106
+ driver: z.ZodEnum<{
107
+ card: "card";
108
+ collect_js: "collect_js";
109
+ stripe_elements: "stripe_elements";
110
+ redirect: "redirect";
111
+ solana_pay: "solana_pay";
112
+ }>;
113
+ psp_key: z.ZodOptional<z.ZodString>;
114
+ public_config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
115
+ }, z.core.$strip>>;
116
+ saved_methods: z.ZodOptional<z.ZodArray<z.ZodObject<{
117
+ id: z.ZodString;
118
+ option_id: z.ZodString;
119
+ rail: z.ZodString;
120
+ brand: z.ZodOptional<z.ZodString>;
121
+ last_four: z.ZodOptional<z.ZodString>;
122
+ exp_month: z.ZodOptional<z.ZodNumber>;
123
+ exp_year: z.ZodOptional<z.ZodNumber>;
124
+ default: z.ZodOptional<z.ZodBoolean>;
125
+ }, z.core.$strip>>>;
126
+ transaction_url: z.ZodOptional<z.ZodString>;
127
+ payment_id: z.ZodOptional<z.ZodString>;
128
+ subscription_id: z.ZodOptional<z.ZodString>;
129
+ failure_message: z.ZodOptional<z.ZodString>;
130
+ failure: z.ZodOptional<z.ZodNullable<z.ZodObject<{
131
+ reason: z.ZodString;
132
+ message: z.ZodString;
133
+ field: z.ZodOptional<z.ZodNullable<z.ZodString>>;
134
+ }, z.core.$strip>>>;
135
+ operation: z.ZodOptional<z.ZodNullable<z.ZodObject<{
136
+ id: z.ZodString;
137
+ status: z.ZodString;
138
+ }, z.core.$strip>>>;
139
+ success_url: z.ZodOptional<z.ZodString>;
140
+ embed_origin: z.ZodOptional<z.ZodString>;
141
+ expires_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
142
+ }, z.core.$strip>;
143
+ export type CheckoutSession = z.infer<typeof checkoutSessionSchema>;
144
+ export declare const payRequestSchema: z.ZodObject<{
145
+ option_id: z.ZodString;
146
+ payment_token: z.ZodOptional<z.ZodString>;
147
+ payment_method_id: z.ZodOptional<z.ZodString>;
148
+ email: z.ZodOptional<z.ZodString>;
149
+ name_on_card: z.ZodOptional<z.ZodString>;
150
+ address1: z.ZodOptional<z.ZodString>;
151
+ city: z.ZodOptional<z.ZodString>;
152
+ state: z.ZodOptional<z.ZodString>;
153
+ zip: z.ZodOptional<z.ZodString>;
154
+ country: z.ZodOptional<z.ZodString>;
155
+ card: z.ZodOptional<z.ZodObject<{
156
+ number: z.ZodString;
157
+ exp_month: z.ZodNumber;
158
+ exp_year: z.ZodNumber;
159
+ cvc: z.ZodString;
160
+ }, z.core.$strip>>;
161
+ last_four: z.ZodOptional<z.ZodString>;
162
+ card_type: z.ZodOptional<z.ZodString>;
163
+ expiry_date: z.ZodOptional<z.ZodString>;
164
+ token_symbol: z.ZodOptional<z.ZodString>;
165
+ }, z.core.$strip>;
166
+ export type PayRequest = z.infer<typeof payRequestSchema>;
167
+ export declare const payResultSchema: z.ZodObject<{
168
+ status: z.ZodEnum<{
169
+ succeeded: "succeeded";
170
+ failed: "failed";
171
+ expired: "expired";
172
+ created: "created";
173
+ requires_action: "requires_action";
174
+ processing: "processing";
175
+ blocked: "blocked";
176
+ canceled: "canceled";
177
+ }>;
178
+ redirect_url: z.ZodOptional<z.ZodString>;
179
+ transaction_url: z.ZodOptional<z.ZodString>;
180
+ payment_id: z.ZodOptional<z.ZodString>;
181
+ subscription_id: z.ZodOptional<z.ZodString>;
182
+ failure_message: z.ZodOptional<z.ZodString>;
183
+ failure: z.ZodOptional<z.ZodNullable<z.ZodObject<{
184
+ reason: z.ZodString;
185
+ message: z.ZodString;
186
+ field: z.ZodOptional<z.ZodNullable<z.ZodString>>;
187
+ }, z.core.$strip>>>;
188
+ operation_id: z.ZodOptional<z.ZodString>;
189
+ }, z.core.$strip>;
190
+ export type PayResult = z.infer<typeof payResultSchema>;
191
+ export type CheckoutPhase = "loading" | "ready" | "processing" | "succeeded" | "failed" | "blocked" | "expired" | "unavailable" | "error";
@@ -0,0 +1,28 @@
1
+ # NMI host integration and pending checkout states
2
+
3
+ Owner: `/root/astra_neutral_resume`.
4
+ Worktree: `/home/fidika/cozy/.worktrees/billing-ui/1055-nmi-checkout-ui-20260923`.
5
+ Branch: `feat/1055-nmi-checkout-ui-20260923`.
6
+ Base: `05d6e27` from freshly fetched `origin/master`.
7
+
8
+ The demo will reuse this package's existing Collect.js hosted fields through a
9
+ custom CheckoutSource that invokes its admitted application checkout wrapper.
10
+ It will not enable generic billing checkout creation or send raw card data to the
11
+ application. Stripe remains available alongside the explicitly selected NMI PSP.
12
+ Only public tokenization configuration reaches the browser.
13
+
14
+ Bounded library changes:
15
+
16
+ - Represent `processing` as nonterminal server truth. Poll the same source for
17
+ progress without repeating pay or tokenization, including after reload. Local
18
+ quote expiry must not turn an accepted unresolved payment into a failed sale.
19
+ - Expose a tokenized-card form composed from the existing Collect.js driver and
20
+ billing fields, for a host's separately consented save-card step. Do not model
21
+ card setup as a zero-priced purchase or show a payment-success receipt for it.
22
+ - Retain exact string money and existing callbacks. Tests belong to this library;
23
+ the demo uses build/lint and manual qualification only.
24
+
25
+ The existing metadata rename PR15 and customer billing package PR13 remain
26
+ separately owned. Publication uses the GitHub release tarball workflow after review
27
+ and CI; no source checkout path ships in a host dependency manifest. Real NMI
28
+ iframe/tokenization/provider qualification remains a separate browser gate.
@@ -0,0 +1,153 @@
1
+ # Payment form contract
2
+
3
+ `Checkout` renders one real `<form autocomplete="on">` and submits through its
4
+ native submit event. Every host-owned billing field has a stable `id`, `name`,
5
+ label, and standards-based autocomplete token.
6
+
7
+ ## Money
8
+
9
+ The session document (`GET /billing/v1/checkout-sessions/{id}`) carries every amount
10
+ as an exact signed int64 decimal string of `plan.currency`'s native unit, and
11
+ `plan.unit_decimals` is that currency's registered scale:
12
+
13
+ ```json
14
+ {
15
+ "plan": {
16
+ "display_name": "Premium Membership",
17
+ "unit_amount": "99000000",
18
+ "currency": "USD",
19
+ "unit_decimals": 6,
20
+ "period_hours": 720,
21
+ "automatically_renews": true
22
+ },
23
+ "line_items": [{ "label": "Premium Membership", "amount": "99000000" }],
24
+ "tax": "0",
25
+ "due_today": "99000000"
26
+ }
27
+ ```
28
+
29
+ `unit_amount`, `line_items[].amount`, `tax` and `due_today` share the plan's
30
+ currency and scale. A JSON number, a decimal point, a value outside int64, or
31
+ a missing `unit_decimals` fails schema validation and the session is
32
+ unavailable; the package never assumes a scale. Hosts take the scale from
33
+ OpenRails' currency registry (`billing.LookupCurrency`, the same table as
34
+ `GET /v1/currencies`); OpenRails serves the session at
35
+ `GET /v1/checkout-sessions/{id}`; the Go shape is
36
+ `billing.HostedCheckoutSession` and its canonical fixture
37
+ (`testdata/wire/hosted_checkout_session.json`) is decoded by this package's
38
+ tests.
39
+
40
+ Rendering scales with `BigInt` and hands Intl the exact major-unit decimal;
41
+ `formatAmount`, `amountToDecimal` and `addAmounts` are exported for hosts that
42
+ show the same figures outside the component. An amount the engine cannot show
43
+ exactly is refused with a visible notice, never rounded. `due_today`, when
44
+ absent, is the exact sum of the line items and `tax`.
45
+
46
+ Version 0.3.0 replaces `unit_amount_micros`, `amount_micros`, `tax_micros` and
47
+ `due_today_micros` (JSON numbers, assumed six decimals) with this contract.
48
+
49
+ ## NMI new cards
50
+
51
+ The host collects only:
52
+
53
+ - `name_on_card` — one visible “Name on card” input with `autocomplete="cc-name"`;
54
+ - `country` — a native ISO-3166 country select with `autocomplete="billing country"`;
55
+ - `zip` — a country-aware ZIP/postal input with `autocomplete="billing postal-code"`.
56
+
57
+ Postal code is optional for the 53 ISO-3166 alpha-2 countries and territories
58
+ in the Universal Postal Union's September 2025
59
+ [list of countries which do not require postal codes](https://www.upu.int/UPU/media/upu/documents/PostCode/General-Addressing-Issues.pdf).
60
+ The UI keeps a postal code when the customer supplies one. It does not collect
61
+ street, city, or state for NMI.
62
+
63
+ Card number, expiration, and CVC remain inside NMI Collect.js cross-origin
64
+ iframes. NMI owns those inner inputs, including their browser-autofill behavior;
65
+ host markup cannot assign `cc-number`, `cc-exp`, or `cc-csc` to them. The
66
+ Collect.js configuration supplies the supported field titles and placeholders,
67
+ and the outer host page provides stable labelled containers. Verify saved-card
68
+ autofill against the real gateway in each supported browser because a DOM unit
69
+ test cannot inspect or control the cross-origin fields.
70
+
71
+ A PSP that takes cards on OpenRails itself (`card_entry: server`, driver
72
+ `card`) gets plain inputs instead, with `autocomplete` `cc-number`, `cc-exp`
73
+ and `cc-csc`, the same layout and field errors, and no gateway script. Its
74
+ request carries `card: {number, exp_month, exp_year, cvc}` in place of
75
+ `payment_token`; the inputs are cleared once the card is sent.
76
+
77
+ A new-card payment request includes the one-time `payment_token` plus canonical
78
+ `name_on_card`, uppercase ISO country in `country`, and `zip`. Paying with an
79
+ existing saved method sends only `payment_method_id`; it does not overwrite the
80
+ stored billing identity with empty form values.
81
+
82
+ ## CCBill
83
+
84
+ For CCBill credit-card hand-off, the browser supplies only the same single
85
+ `name_on_card`, country, and postal code. OpenRails binds the authenticated
86
+ account's verified email and derives the customer's IP server-side; the browser
87
+ cannot override either value. CCBill's FlexForms Address Fields are
88
+ configurable for credit-card forms, so OpenRails does not collect street, city,
89
+ or state in this flow. The checkout package never exposes separate first- and
90
+ last-name inputs. OpenRails performs any provider-specific name projection at
91
+ the CCBill boundary.
92
+
93
+ Version 0.2.4 requires a checkout host whose pay endpoint accepts
94
+ `name_on_card`. Legacy `first_name` and `last_name` are no longer emitted by
95
+ this package.
96
+
97
+ ## Card subscriptions
98
+
99
+ `Subscribe` is one action. The host's pay endpoint relays `payment_token` (or
100
+ `payment_method_id`) to `CreateCheckoutSession` with `Confirm: true`; OpenRails
101
+ saves a new card, accepts the displayed recurring terms and charges it. A
102
+ decline answers `failed` and keeps no card; the next attempt uses a new key.
103
+
104
+ ## Solana Pay
105
+
106
+ The package never assumes which token a Solana option settles in. OpenRails
107
+ binds the token on the advertised option's `public_config` (hosts copy it):
108
+
109
+ - `token_symbol` — required; the SPL mint symbol the price is bound to
110
+ (`USDC`, `USD1`, …). It is sent back verbatim (uppercased) as the pay
111
+ request's `token_symbol`. An option without it is not offered at all.
112
+ - `token_name` — optional buyer-facing name (`USD Coin`), shown next to the
113
+ symbol in the method list.
114
+ - `network` — optional Solana cluster (`mainnet-beta`, `devnet`, `testnet`).
115
+ Any network other than `mainnet-beta` is named in the method hint
116
+ (“USD Coin (USDC) on Solana devnet”) so a test-network payment is never
117
+ mistaken for a real one.
118
+
119
+ Version 0.2.5 removes the former `USDC` default: a host that bound no token
120
+ saw “USDC” before and sees no Solana option now.
121
+
122
+ ## Pending payment outcomes
123
+
124
+ `processing` means an accepted outcome is unresolved. Checkout polls the same
125
+ source's `getSession` without calling `pay` or Collect.js again. The optional
126
+ `expires_at` may be null for an accepted pending attempt; a local display timer
127
+ never releases or fails that attempt. The host must return verified terminal
128
+ state, not infer payment from navigation or a timeout. A thrown error after
129
+ `pay` starts is treated as unknown and remains pending, rather than enabling
130
+ a fresh card token and payment. Tokenization/field validation errors before
131
+ submission remain editable. Explicit `failed` results are definitive host
132
+ responses; they must not represent an unknown provider result.
133
+
134
+ A custom CheckoutSource may call an application-owned admission wrapper instead
135
+ of the default hosted endpoints. It must retain its logical attempt identity
136
+ and original request, use the same key for an authorized retry, and expose only
137
+ the authorized buyer's state. `getSession` is a read, not another payment.
138
+
139
+ ## Separate NMI card setup
140
+
141
+ `TokenizedCardForm` reuses the same hosted fields and emits `payment_token`,
142
+ `name_on_card`, `country` and `zip` to its async `onTokenized` callback. The host
143
+ provides explicit save-card consent and uses the callback to invoke its native
144
+ authenticated save-card endpoint. Set `disabled` until consent is given. The
145
+ form never creates a zero-dollar checkout or announces a completed payment.
146
+ After token submission it locks the form; a failed/unknown save must be checked
147
+ against the host's saved-method state before mounting a new attempt.
148
+
149
+ Saving a card does not authorize a recurring charge. The host separately obtains
150
+ and displays the immutable membership quote, then asks the signed-in customer
151
+ to confirm that agreement.
152
+
153
+ Hosts must key `TokenizedCardForm` by authenticated customer and provider identity. A customer change must unmount the old form. The form also discards an in-flight token if its tokenization configuration changes or save consent is withdrawn before dispatch.
package/package.json CHANGED
@@ -1,6 +1,115 @@
1
1
  {
2
2
  "name": "@openrails/billing-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.230.0",
4
+ "description": "Embeddable OpenRails checkout and account-billing UI: client, React hooks and styled components.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/open-rails/openrails.git",
10
+ "directory": "sdk/billing-ui"
11
+ },
12
+ "homepage": "https://github.com/open-rails/openrails/tree/master/sdk/billing-ui#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/open-rails/openrails/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=22.12.0"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "docs",
22
+ "CHANGELOG.md",
23
+ "LICENSE",
24
+ "README.md"
25
+ ],
26
+ "sideEffects": [
27
+ "./dist/index.js",
28
+ "**/*.css"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./client": {
36
+ "types": "./dist/client/index.d.ts",
37
+ "import": "./dist/client.js"
38
+ },
39
+ "./react": {
40
+ "types": "./dist/react/index.d.ts",
41
+ "import": "./dist/react.js"
42
+ },
43
+ "./locales/*": {
44
+ "types": "./dist/locales/*.d.ts",
45
+ "import": "./dist/locales/*.js"
46
+ },
47
+ "./styles.css": "./dist/styles.css",
48
+ "./package.json": "./package.json"
49
+ },
50
+ "main": "./dist/index.js",
51
+ "module": "./dist/index.js",
52
+ "types": "./dist/index.d.ts",
53
+ "style": "./dist/styles.css",
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": false
57
+ },
58
+ "peerDependencies": {
59
+ "react": ">=19 <20",
60
+ "react-dom": ">=19 <20"
61
+ },
62
+ "dependencies": {
63
+ "@base-ui/react": "^1.7.0",
64
+ "@hugeicons/core-free-icons": "^4.2.3",
65
+ "@hugeicons/react": "^1.1.9",
66
+ "@stripe/stripe-js": "^9.17.0",
67
+ "class-variance-authority": "^0.7.1",
68
+ "cn": "0.4.0",
69
+ "zod": "^4.4.3"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^10.0.1",
73
+ "@microsoft/api-extractor": "^7.58.12",
74
+ "@playwright/test": "1.60.0",
75
+ "@solana/kit": "^6.10.0",
76
+ "@solana/pay": "^1.0.26",
77
+ "@solana/sysvars": "^5.5.1",
78
+ "@tailwindcss/vite": "^4.3.3",
79
+ "@testing-library/jest-dom": "^7.0.0",
80
+ "@testing-library/react": "^16.3.2",
81
+ "@types/node": "^26.2.0",
82
+ "@types/react": "^19.2.18",
83
+ "@types/react-dom": "^19.2.4",
84
+ "@vitejs/plugin-react": "^6.0.5",
85
+ "eslint": "^10.8.1",
86
+ "eslint-plugin-react-hooks": "^7.1.1",
87
+ "eslint-plugin-react-refresh": "^0.5.3",
88
+ "globals": "^17.9.0",
89
+ "jsdom": "^30.0.1",
90
+ "postcss": "8.5.26",
91
+ "prettier": "^3.9.6",
92
+ "prettier-plugin-tailwindcss": "^0.8.1",
93
+ "react": "^19.2.8",
94
+ "react-dom": "^19.2.8",
95
+ "tailwindcss": "^4.3.3",
96
+ "tw-animate-css": "^1.4.0",
97
+ "typescript": "^5.9.3",
98
+ "typescript-eslint": "^8.66.0",
99
+ "vite": "^8.2.1",
100
+ "vite-plugin-dts": "^5.0.3",
101
+ "vitest": "^4.1.11"
102
+ },
103
+ "scripts": {
104
+ "build": "vite build",
105
+ "typecheck": "tsc --noEmit",
106
+ "lint": "eslint .",
107
+ "test": "vitest run",
108
+ "test:browser": "playwright test",
109
+ "test:e2e": "playwright test -c playwright.e2e.config.ts",
110
+ "contract": "node e2e/scripts/contract.ts",
111
+ "contract:check": "pnpm contract && git diff --exit-code -- src/client/generated && test -z \"$(git status --porcelain -- src/client/generated)\"",
112
+ "format": "prettier --write \"**/*.{css,ts,tsx,json,md}\"",
113
+ "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
114
+ }
6
115
  }