@getruba/checkout 0.3.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.
- package/README.md +221 -0
- package/dist/chunk-FSZEYAE6.js +1 -0
- package/dist/chunk-GBQE5XCD.js +1 -0
- package/dist/chunk-TS7YIDY3.js +1 -0
- package/dist/chunk-ZWRDP37E.js +1 -0
- package/dist/components/index.cjs +113 -0
- package/dist/components/index.d.cts +95 -0
- package/dist/components/index.d.ts +95 -0
- package/dist/components/index.js +113 -0
- package/dist/embed.cjs +1 -0
- package/dist/embed.d.cts +55 -0
- package/dist/embed.d.ts +55 -0
- package/dist/embed.global.js +1 -0
- package/dist/embed.js +1 -0
- package/dist/guards.cjs +1 -0
- package/dist/guards.d.cts +19 -0
- package/dist/guards.d.ts +19 -0
- package/dist/guards.js +1 -0
- package/dist/hooks/index.cjs +1 -0
- package/dist/hooks/index.d.cts +6 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +1 -0
- package/dist/index.d-DvLuMU7T.d.cts +29754 -0
- package/dist/index.d-DvLuMU7T.d.ts +29754 -0
- package/dist/payment-method.cjs +1 -0
- package/dist/payment-method.d.cts +86 -0
- package/dist/payment-method.d.ts +86 -0
- package/dist/payment-method.js +1 -0
- package/dist/providers/index.cjs +1 -0
- package/dist/providers/index.d.cts +89 -0
- package/dist/providers/index.d.ts +89 -0
- package/dist/providers/index.js +1 -0
- package/dist/react/payment-method.cjs +1 -0
- package/dist/react/payment-method.d.cts +25 -0
- package/dist/react/payment-method.d.ts +25 -0
- package/dist/react/payment-method.js +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# `@getruba/checkout`
|
|
2
|
+
|
|
3
|
+
JavaScript utilities for embedding Ruba into your website. Drop in the single CDN script (auto-init) or import per-feature from npm for tree-shaking.
|
|
4
|
+
|
|
5
|
+
## Payment Method
|
|
6
|
+
|
|
7
|
+
A customer session token authenticates every embed. Create it **on your server** — your Ruba access token must stay secret — then hand the token to the browser. The SDK accepts either a `ruba_cst_*` or `ruba_mst_*` prefix and routes internally.
|
|
8
|
+
|
|
9
|
+
### Javascript
|
|
10
|
+
|
|
11
|
+
#### Modal
|
|
12
|
+
|
|
13
|
+
A full-screen overlay the SDK creates and tears down for you — open it on demand, e.g. from a button click.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Ruba } from '@getruba/sdk'
|
|
17
|
+
import { RubaEmbedPaymentMethod } from '@getruba/checkout/payment-method'
|
|
18
|
+
|
|
19
|
+
const ruba = new Ruba({ accessToken: process.env.RUBA_ACCESS_TOKEN })
|
|
20
|
+
const session = await ruba.customerSessions.create({
|
|
21
|
+
customerId: 'ABC-123',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const embed = await RubaEmbedPaymentMethod.create({
|
|
25
|
+
sessionToken: session.token,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
embed.addEventListener('success', (event) => {
|
|
29
|
+
console.log('Attached:', event.detail.paymentMethodId)
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### `create()` options
|
|
34
|
+
|
|
35
|
+
| Option | Type | Default | Description |
|
|
36
|
+
| -------------- | ------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `sessionToken` | `string` | — | **Required.** Session token from `POST /v1/customer-sessions` (`ruba_cst_*` or `ruba_mst_*`). |
|
|
38
|
+
| `theme` | `'light' \| 'dark'` | `light` | Colour scheme for the embed. |
|
|
39
|
+
| `setAsDefault` | `boolean` | `true` | Whether the new card should become the customer's default payment method. |
|
|
40
|
+
| `returnUrl` | `string` | current URL | Where to return the customer after a redirect-based payment method (Amazon Pay etc). Defaults to `window.location.href`. See [Redirect re-entry](#redirect-re-entry). |
|
|
41
|
+
| `locale` | `string` | `'en'` | BCP47 locale for the embed UI and Stripe Elements (e.g. `'en'`, `'fr-FR'`). Unsupported locales fall back to English. |
|
|
42
|
+
| `onLoaded` | `(event: CustomEvent) => void` | — | Convenience callback for the `loaded` event. Equivalent to `embed.addEventListener('loaded', …)`. |
|
|
43
|
+
|
|
44
|
+
#### Inline
|
|
45
|
+
|
|
46
|
+
A chrome-less, auto-resizing iframe mounted into an element you control — compose it into your own layout.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Ruba } from '@getruba/sdk'
|
|
50
|
+
import { RubaEmbedPaymentMethod } from '@getruba/checkout/payment-method'
|
|
51
|
+
|
|
52
|
+
const ruba = new Ruba({ accessToken: process.env.RUBA_ACCESS_TOKEN })
|
|
53
|
+
const session = await ruba.customerSessions.create({
|
|
54
|
+
customerId: 'ABC-123',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const embed = RubaEmbedPaymentMethod.createInline({
|
|
58
|
+
sessionToken: session.token,
|
|
59
|
+
element: document.getElementById('ruba-payment-method')!,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
embed.addEventListener('success', (event) => {
|
|
63
|
+
console.log('Attached:', event.detail.paymentMethodId)
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### `createInline()` options
|
|
68
|
+
|
|
69
|
+
| Option | Type | Default | Description |
|
|
70
|
+
| -------------- | ------------------------------ | ------- | ------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `sessionToken` | `string` | — | **Required.** Session token from `POST /v1/customer-sessions` (`ruba_cst_*` or `ruba_mst_*`). |
|
|
72
|
+
| `element` | `HTMLElement` | — | **Required.** The element to mount the embed into. Any existing children are replaced. |
|
|
73
|
+
| `theme` | `'light' \| 'dark'` | `light` | Colour scheme for the embed. |
|
|
74
|
+
| `setAsDefault` | `boolean` | `true` | Whether the new card should become the customer's default payment method. |
|
|
75
|
+
| `locale` | `string` | `'en'` | BCP47 locale for the embed UI and Stripe Elements. Unsupported locales fall back to English. |
|
|
76
|
+
| `onLoaded` | `(event: CustomEvent) => void` | — | Convenience callback for the `loaded` event. Equivalent to `embed.addEventListener('loaded', …)`. |
|
|
77
|
+
|
|
78
|
+
#### Events
|
|
79
|
+
|
|
80
|
+
All events are dispatched as cancelable `CustomEvent`s on the `embed` instance. Call `event.preventDefault()` to opt out of the SDK's default action.
|
|
81
|
+
|
|
82
|
+
| Event | Detail | Default action |
|
|
83
|
+
| ----------- | ----------------------------- | --------------------------------------------------------------------- |
|
|
84
|
+
| `loaded` | — | Removes the loader spinner once the iframe is ready. |
|
|
85
|
+
| `close` | — | Tears down the iframe (unless locked by a pending `confirmed`). |
|
|
86
|
+
| `confirmed` | — | Marks the modal as non-closable while Stripe is processing. |
|
|
87
|
+
| `success` | `{ paymentMethodId: string }` | **Auto-closes the modal.** Call `preventDefault()` to keep it open. |
|
|
88
|
+
| `error` | `{ code: ErrorCode }` | None. `ErrorCode = 'invalid_request' \| 'unauthorized' \| 'unknown'`. |
|
|
89
|
+
|
|
90
|
+
#### Instance methods
|
|
91
|
+
|
|
92
|
+
| Method | Description |
|
|
93
|
+
| ------------------------------------------- | ------------------------------------------------------- |
|
|
94
|
+
| `embed.close()` | Programmatically close the modal and remove the iframe. |
|
|
95
|
+
| `embed.addEventListener(type, listener)` | Subscribe to an event. Returns `void`. |
|
|
96
|
+
| `embed.removeEventListener(type, listener)` | Unsubscribe. |
|
|
97
|
+
|
|
98
|
+
#### Redirect re-entry
|
|
99
|
+
|
|
100
|
+
Redirect-based payment methods (Amazon Pay etc, etc) authorise on the provider's own site — the browser navigates the whole tab away and back to `returnUrl` (defaults to the page the SDK was opened from), so the modal can't survive the round-trip. Read the outcome on the returned page with the static `getRedirectResult()`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const result = RubaEmbedPaymentMethod.getRedirectResult()
|
|
104
|
+
// result: { status: 'succeeded' | 'failed' } | null
|
|
105
|
+
|
|
106
|
+
if (result?.status === 'succeeded') {
|
|
107
|
+
// refresh the customer's payment methods
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
It strips the status param from the URL so a refresh won't resurface a stale result. Card payments (3DS) complete inside the modal and never trigger this path.
|
|
112
|
+
|
|
113
|
+
### React
|
|
114
|
+
|
|
115
|
+
#### Modal
|
|
116
|
+
|
|
117
|
+
Open the full-screen modal with `RubaEmbedPaymentMethod.create()` — the same API as vanilla JS, called from a Client Component event handler.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { RubaEmbedPaymentMethod } from '@getruba/checkout/payment-method'
|
|
121
|
+
|
|
122
|
+
export function AddPaymentMethodButton({
|
|
123
|
+
sessionToken,
|
|
124
|
+
}: {
|
|
125
|
+
sessionToken: string
|
|
126
|
+
}) {
|
|
127
|
+
const openEmbed = async () => {
|
|
128
|
+
const embed = await RubaEmbedPaymentMethod.create({ sessionToken })
|
|
129
|
+
embed.addEventListener('success', (event) => {
|
|
130
|
+
console.log('Attached:', event.detail.paymentMethodId)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return <button onClick={openEmbed}>Add payment method</button>
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Inline
|
|
139
|
+
|
|
140
|
+
Render `<RubaPaymentMethod />` for a chrome-less, auto-resizing embed inside your own layout.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { Ruba } from '@getruba/sdk'
|
|
144
|
+
import { RubaPaymentMethod } from '@getruba/checkout/react/payment-method'
|
|
145
|
+
|
|
146
|
+
const ruba = new Ruba({ accessToken: process.env.RUBA_ACCESS_TOKEN })
|
|
147
|
+
const session = await ruba.customerSessions.create({
|
|
148
|
+
customerId: 'ABC-123',
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<RubaPaymentMethod
|
|
153
|
+
sessionToken={session.token}
|
|
154
|
+
onSuccess={(id) => console.log('Attached:', id)}
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Props
|
|
160
|
+
|
|
161
|
+
| Prop | Type | Default | Description |
|
|
162
|
+
| -------------- | ----------------------------------- | ------- | --------------------------------------------------------------------------------- |
|
|
163
|
+
| `sessionToken` | `string` | — | **Required.** Session token from `POST /v1/customer-sessions`. |
|
|
164
|
+
| `theme` | `'light' \| 'dark'` | `light` | Colour scheme. |
|
|
165
|
+
| `setAsDefault` | `boolean` | `true` | Whether the new card should become the customer's default payment method. |
|
|
166
|
+
| `locale` | `string` | `'en'` | BCP47 locale (e.g. `'en'`, `'fr-FR'`). Unsupported locales fall back to English. |
|
|
167
|
+
| `onLoaded` | `() => void` | — | Fires once when the iframe finishes loading and the form becomes interactive. |
|
|
168
|
+
| `onConfirmed` | `() => void` | — | Fires when the customer submits and Stripe processing starts. |
|
|
169
|
+
| `onSuccess` | `(paymentMethodId: string) => void` | — | Fires after the card has been attached to the customer. |
|
|
170
|
+
| `onError` | `(code: ErrorCode) => void` | — | Fires when the iframe can't render (token missing/expired). `ErrorCode` as above. |
|
|
171
|
+
| `className` | `string` | — | Applied to the wrapping `<div>`. Use it to size or position the embed. |
|
|
172
|
+
| `style` | `React.CSSProperties` | — | Inline style on the wrapping `<div>`. |
|
|
173
|
+
|
|
174
|
+
#### Redirect re-entry
|
|
175
|
+
|
|
176
|
+
Redirect-based payment methods (Amazon Pay etc etc.) navigate the whole tab away to the provider and back. Read the outcome on the returned page with the `usePaymentMethodRedirectResult` hook:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { usePaymentMethodRedirectResult } from '@getruba/checkout/react/payment-method'
|
|
180
|
+
|
|
181
|
+
usePaymentMethodRedirectResult({
|
|
182
|
+
onSuccess: () => toast('Payment method added'),
|
|
183
|
+
onError: () => toast('Could not add payment method'),
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
It reads the result once on mount and strips the status param from the URL. Card payments (3DS) complete inside the embed and never trigger this path.
|
|
188
|
+
|
|
189
|
+
### Code snippet
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { Ruba } from '@getruba/sdk'
|
|
193
|
+
|
|
194
|
+
const ruba = new Ruba({ accessToken: process.env.RUBA_ACCESS_TOKEN })
|
|
195
|
+
const session = await ruba.customerSessions.create({
|
|
196
|
+
customerId: 'ABC-123',
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<script
|
|
202
|
+
defer
|
|
203
|
+
data-auto-init
|
|
204
|
+
src="https://cdn.jsdelivr.net/npm/@getruba/checkout@latest/dist/embed.global.js"
|
|
205
|
+
></script>
|
|
206
|
+
|
|
207
|
+
<!-- session.token rendered into the attribute server-side -->
|
|
208
|
+
<button data-ruba-payment-method="ruba_cst_…">Add payment method</button>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The same script also powers `RubaEmbedCheckout` triggers — one tag covers every Ruba embed.
|
|
212
|
+
|
|
213
|
+
#### Attributes
|
|
214
|
+
|
|
215
|
+
| Attribute | Value | Description |
|
|
216
|
+
| ----------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `data-ruba-payment-method` | `string` | **Required.** The session token. Clicking the element opens the modal. |
|
|
218
|
+
| `data-ruba-payment-method-theme` | `light \| dark` | Optional theme override. |
|
|
219
|
+
| `data-ruba-payment-method-set-as-default` | `true \| false` | Optional. Default `true`. Passing `"false"` adds the card without overriding the existing default. |
|
|
220
|
+
| `data-ruba-payment-method-return-url` | `string` | Optional. Return URL for redirect-based payment methods. Defaults to the current page. |
|
|
221
|
+
| `data-ruba-payment-method-locale` | `string` | Optional. BCP47 locale (e.g. `'en'`, `'fr-FR'`). Unsupported locales fall back to English. |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e="RUBA_PAYMENT_METHOD",t="/embed/payment-method",n="ruba_payment_method_status",a=()=>{const e="http://127.0.0.1:3000".split(",");return"undefined"!=typeof window&&e.includes(window.location.origin)?window.location.origin:e[0]},s=()=>{const e="http://127.0.0.1:3000".split(",").join(" ");return`payment 'self' ${e}; publickey-credentials-get 'self' ${e};`},o=class o{iframe;mode;loader;loaded;closable;eventTarget;windowMessageListener;constructor(e,t,n){this.iframe=e,this.mode=t,this.loader=n,this.loaded=!1,this.closable=!0,this.eventTarget=new EventTarget,this.windowMessageListener=this.handleWindowMessage.bind(this),window.addEventListener("message",this.windowMessageListener)}static postMessage(t,n){window.parent.postMessage({...t,type:e},n)}static create(e){const{sessionToken:n,theme:r,setAsDefault:i,returnUrl:d,locale:l,onLoaded:c}=e,h=document.createElement("style");h.innerText=`\n .ruba-loader-spinner {\n width: 20px;\n aspect-ratio: 1;\n border-radius: 50%;\n background: ${"dark"===r?"#000":"#fff"};\n box-shadow: 0 0 0 0 ${"dark"===r?"#fff":"#000"};\n animation: ruba-loader-spinner-animation 1s infinite;\n }\n @keyframes ruba-loader-spinner-animation {\n 100% {box-shadow: 0 0 0 30px #0000}\n }\n body.ruba-no-scroll {\n overflow: hidden;\n }\n `,document.head.appendChild(h);const m=document.createElement("div");m.style.position="absolute",m.style.top="50%",m.style.left="50%",m.style.transform="translate(-50%, -50%)",m.style.zIndex="2147483647",m.style.colorScheme="auto";const u=document.createElement("div");u.className="ruba-loader-spinner",m.appendChild(u),document.body.classList.add("ruba-no-scroll"),document.body.appendChild(m);const b=new URL(t,a());b.searchParams.set("session_token",n),b.searchParams.set("embed","true"),b.searchParams.set("embed_origin",window.location.origin),b.searchParams.set("mode","modal"),b.searchParams.set("embed_return_url",d??window.location.href),r&&b.searchParams.set("theme",r),!1===i&&b.searchParams.set("set_default","false"),l&&b.searchParams.set("locale",l);const w=document.createElement("iframe");w.src=b.toString(),w.style.position="fixed",w.style.top="0",w.style.left="0",w.style.width="100%",w.style.height="100%",w.style.border="none",w.style.zIndex="2147483647",w.style.backgroundColor="rgba(0, 0, 0, 0.5)",w.style.colorScheme="auto",w.allow=s(),document.body.appendChild(w);const f=new o(w,"modal",m);return c&&f.addEventListener("loaded",c,{once:!0}),new Promise(e=>{f.addEventListener("loaded",()=>e(f),{once:!0})})}static createInline(e){const{sessionToken:n,element:r,theme:i,setAsDefault:d,locale:l,onLoaded:c}=e,h=new URL(t,a());h.searchParams.set("session_token",n),h.searchParams.set("embed","true"),h.searchParams.set("embed_origin",window.location.origin),h.searchParams.set("mode","inline"),i&&h.searchParams.set("theme",i),!1===d&&h.searchParams.set("set_default","false"),l&&h.searchParams.set("locale",l);const m=document.createElement("iframe");m.src=h.toString(),m.style.display="block",m.style.width="100%",m.style.height="0",m.style.border="none",m.style.colorScheme="auto",m.allow=s(),r.replaceChildren(m);const u=new o(m,"inline",null);return c&&u.addEventListener("loaded",c,{once:!0}),u}static init(){document.querySelectorAll("[data-ruba-payment-method]").forEach(e=>{e.removeEventListener("click",o.elementClickHandler),e.addEventListener("click",o.elementClickHandler)})}static getRedirectResult(){if("undefined"==typeof window)return null;const e=new URLSearchParams(window.location.search),t=e.get(n);if("succeeded"!==t&&"failed"!==t)return null;e.delete(n);const a=e.toString();return window.history.replaceState({},"",`${window.location.pathname}${a?`?${a}`:""}${window.location.hash}`),{status:t}}close(){window.removeEventListener("message",this.windowMessageListener),this.iframe.remove(),"modal"===this.mode&&document.body.classList.remove("ruba-no-scroll")}addEventListener(e,t,n){this.eventTarget.addEventListener(e,t,n)}removeEventListener(e,t){this.eventTarget.removeEventListener(e,t)}static async elementClickHandler(e){e.preventDefault();let t=e.target;for(;!t.hasAttribute("data-ruba-payment-method");){if(!t.parentElement)return;t=t.parentElement}const n=t.getAttribute("data-ruba-payment-method");if(!n)return;const a=t.getAttribute("data-ruba-payment-method-theme"),s=t.getAttribute("data-ruba-payment-method-set-as-default"),r=t.getAttribute("data-ruba-payment-method-return-url"),i=t.getAttribute("data-ruba-payment-method-locale");o.create({sessionToken:n,theme:a??void 0,setAsDefault:null===s?void 0:"false"!==s,returnUrl:r??void 0,locale:i??void 0})}handleLoaded(){this.loaded||(this.loader&&document.body.contains(this.loader)&&document.body.removeChild(this.loader),this.loaded=!0)}handleClose(){this.closable&&this.close()}handleConfirmed(){this.closable=!1}handleSuccess(){"inline"!==this.mode&&(this.closable=!0,this.close())}handleError(){this.closable=!0}handleWindowMessage({data:t,origin:n}){if(!"http://127.0.0.1:3000".split(",").includes(n))return;if(t.type!==e)return;if("resize"===t.event)return void("inline"===this.mode&&(this.iframe.style.height=`${Math.max(0,Math.ceil(t.height))}px`));const a=new CustomEvent(t.event,{detail:t,cancelable:!0});if(this.eventTarget.dispatchEvent(a),!a.defaultPrevented)switch(t.event){case"loaded":this.handleLoaded();break;case"close":this.handleClose();break;case"confirmed":this.handleConfirmed();break;case"success":this.handleSuccess();break;case"error":this.handleError()}}};if("undefined"!=typeof window&&(window.Ruba={...window.Ruba??{},EmbedPaymentMethod:o}),"undefined"!=typeof document){const e=document.currentScript;e&&e.hasAttribute("data-auto-init")&&document.addEventListener("DOMContentLoaded",()=>{o.init()})}export{n as REDIRECT_STATUS_PARAM,o as EmbedPaymentMethod};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i=r=>r.product!==null&&r.prices!==null,t=r=>r.type==="recurring",u=r=>{if(!r.product||!r.prices)return null;let e=r.prices[r.product.id];return e?e.filter(c=>c.price_currency===r.currency).find(c=>c.amount_type==="seat_based")??null:null},s=r=>{if(!r.product||!r.prices)return null;let e=r.prices[r.product.id];return e?e.filter(c=>c.price_currency===r.currency).find(c=>c.amount_type==="fixed")??null:null};export{i as a,t as b,u as c,s as d};
|