@basis-theory/web-elements 3.0.0-beta.2 → 3.0.0-beta.3
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 +237 -562
- package/dist/.vite/manifest.json +7 -7
- package/dist/basis-theory-sdk.es.js +1 -1
- package/dist/basis-theory-sdk.umd.js +1 -1
- package/dist/{create-card-BuOvimjH.js → create-card-DipDWgph.js} +1 -1
- package/dist/{create-card-BuOvimjH.js.map → create-card-DipDWgph.js.map} +1 -1
- package/dist/{index-oREjBjga.js → index-BBfuUaLw.js} +4 -4
- package/dist/{index-oREjBjga.js.map → index-BBfuUaLw.js.map} +1 -1
- package/dist/loader/basis-theory.min.js +1 -1
- package/dist/sri.json +7 -7
- package/dist/stats.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,669 +1,344 @@
|
|
|
1
|
-
# @basis-theory-elements
|
|
1
|
+
# @basis-theory/web-elements
|
|
2
2
|
|
|
3
|
-
Secure, PCI-compliant
|
|
3
|
+
Secure, PCI-compliant iframe elements for collecting sensitive data in web applications. Card data never touches your servers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### npm
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- Chrome, Firefox, Safari, Edge (desktop)
|
|
14
|
-
- Chrome, Safari (mobile browsers)
|
|
15
|
-
- Standard `window.postMessage` API required
|
|
16
|
-
|
|
17
|
-
❌ **Not Yet Supported:**
|
|
18
|
-
|
|
19
|
-
- iOS/Android Native WebViews
|
|
20
|
-
- React Native WebView
|
|
21
|
-
- Electron (may work, untested)
|
|
22
|
-
|
|
23
|
-
**See [v3 Documentation](../../docs/README.md) for architecture and platform roadmap.**
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 📦 **Installation**
|
|
9
|
+
```bash
|
|
10
|
+
npm install @basis-theory/web-elements@beta
|
|
11
|
+
```
|
|
28
12
|
|
|
29
|
-
###
|
|
13
|
+
### yarn
|
|
30
14
|
|
|
31
15
|
```bash
|
|
32
|
-
|
|
16
|
+
yarn add @basis-theory/web-elements@beta
|
|
33
17
|
```
|
|
34
18
|
|
|
35
|
-
###
|
|
19
|
+
### CDN
|
|
36
20
|
|
|
37
21
|
```html
|
|
38
|
-
|
|
39
|
-
<script src="https://cdn.basistheory.com/elements/v3/index.umd.js"></script>
|
|
22
|
+
<script src="https://js.basistheory.com/3.0.0-beta.2/web-elements/basis-theory.js"></script>
|
|
40
23
|
```
|
|
41
24
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- Modern browser with `iframe` and `postMessage` support
|
|
45
|
-
- TypeScript 4.5+ (optional but recommended for ESM)
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## 🚀 **Quick Start**
|
|
50
|
-
|
|
51
|
-
### **ESM (bundlers)**
|
|
25
|
+
## Quick Start
|
|
52
26
|
|
|
53
27
|
```typescript
|
|
54
|
-
import BasisTheory from '@basis-theory-elements
|
|
55
|
-
|
|
56
|
-
// 1. Initialize SDK
|
|
57
|
-
const bt = BasisTheory('pk_test_YOUR_API_KEY');
|
|
58
|
-
|
|
59
|
-
// 2. Create elements
|
|
60
|
-
const cardNumber = bt.createElement('cardNumber', {
|
|
61
|
-
placeholder: '4111 1111 1111 1111',
|
|
62
|
-
style: {
|
|
63
|
-
base: {
|
|
64
|
-
color: '#000',
|
|
65
|
-
fontSize: '16px',
|
|
66
|
-
fontFamily: 'Arial, sans-serif',
|
|
67
|
-
},
|
|
68
|
-
invalid: {
|
|
69
|
-
color: '#EA4B97',
|
|
70
|
-
},
|
|
71
|
-
complete: {
|
|
72
|
-
color: '#1976d2',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
});
|
|
28
|
+
import BasisTheory from '@basis-theory/web-elements';
|
|
76
29
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
style: {
|
|
80
|
-
/* ... */
|
|
81
|
-
},
|
|
82
|
-
});
|
|
30
|
+
// Initialize (synchronous -- returns immediately)
|
|
31
|
+
const bt = BasisTheory('pk_test_...');
|
|
83
32
|
|
|
84
|
-
|
|
33
|
+
// Create elements
|
|
34
|
+
const cardNumberEl = bt.createElement('cardNumber', {
|
|
35
|
+
placeholder: '4242 4242 4242 4242',
|
|
36
|
+
});
|
|
37
|
+
const expiryEl = bt.createElement('expiry', {
|
|
85
38
|
placeholder: 'MM/YY',
|
|
86
|
-
style: {
|
|
87
|
-
/* ... */
|
|
88
|
-
},
|
|
89
39
|
});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await cardNumber.mount('#card-number');
|
|
93
|
-
await cvv.mount('#cvv');
|
|
94
|
-
await expiry.mount('#expiry');
|
|
95
|
-
|
|
96
|
-
// 4. Listen for changes
|
|
97
|
-
cardNumber.on('change', (event) => {
|
|
98
|
-
console.log('Card Brand:', event.detail.cardBrand);
|
|
99
|
-
console.log('Is Valid:', event.detail.isValid);
|
|
100
|
-
console.log('Is Complete:', event.detail.isComplete);
|
|
40
|
+
const cvvEl = bt.createElement('cvv', {
|
|
41
|
+
placeholder: '123',
|
|
101
42
|
});
|
|
102
43
|
|
|
103
|
-
|
|
104
|
-
|
|
44
|
+
// Mount to DOM (async -- creates secure iframes)
|
|
45
|
+
await Promise.all([
|
|
46
|
+
cardNumberEl.mount('#card-number'),
|
|
47
|
+
expiryEl.mount('#expiry'),
|
|
48
|
+
cvvEl.mount('#cvv'),
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// Listen for validation changes
|
|
52
|
+
cardNumberEl.on('change', (event) => {
|
|
53
|
+
console.log('Valid:', event.detail.isValid);
|
|
54
|
+
console.log('Brand:', event.detail.cardBrand);
|
|
105
55
|
});
|
|
106
56
|
|
|
107
|
-
//
|
|
108
|
-
const token = await bt.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
57
|
+
// Tokenize on form submission
|
|
58
|
+
const token = await bt.tokens.create({
|
|
59
|
+
type: 'card',
|
|
60
|
+
data: {
|
|
61
|
+
number: cardNumberEl,
|
|
62
|
+
expiration_month: expiryEl,
|
|
63
|
+
expiration_year: expiryEl,
|
|
64
|
+
cvc: cvvEl,
|
|
65
|
+
},
|
|
112
66
|
});
|
|
113
67
|
|
|
114
68
|
console.log('Token ID:', token.id);
|
|
115
|
-
console.log('Masked Card:', token.mask);
|
|
116
69
|
```
|
|
117
70
|
|
|
118
|
-
###
|
|
71
|
+
### CDN Usage
|
|
119
72
|
|
|
120
73
|
```html
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Create elements
|
|
138
|
-
const cardNumber = bt.createElement('cardNumber', {
|
|
139
|
-
placeholder: '4111 1111 1111 1111',
|
|
140
|
-
style: {
|
|
141
|
-
base: { color: '#000', fontSize: '16px' },
|
|
142
|
-
invalid: { color: '#EA4B97' },
|
|
143
|
-
complete: { color: '#1976d2' },
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const cvv = bt.createElement('cvv', { placeholder: '123' });
|
|
148
|
-
const expiry = bt.createElement('expiry', { placeholder: 'MM/YY' });
|
|
149
|
-
|
|
150
|
-
// Mount elements
|
|
151
|
-
cardNumber.mount('#card-number').then(() => {
|
|
152
|
-
console.log('Card number mounted');
|
|
153
|
-
});
|
|
154
|
-
cvv.mount('#cvv');
|
|
155
|
-
expiry.mount('#expiry');
|
|
156
|
-
|
|
157
|
-
// Tokenize on button click
|
|
158
|
-
document
|
|
159
|
-
.getElementById('tokenize-btn')
|
|
160
|
-
.addEventListener('click', async () => {
|
|
161
|
-
const token = await bt.tokenize({
|
|
162
|
-
number: cardNumber,
|
|
163
|
-
cvv: cvv,
|
|
164
|
-
expiry: expiry,
|
|
165
|
-
});
|
|
166
|
-
console.log('Token:', token);
|
|
167
|
-
});
|
|
168
|
-
</script>
|
|
169
|
-
</body>
|
|
170
|
-
</html>
|
|
74
|
+
<script src="https://js.basistheory.com/3.0.0-beta.2/web-elements/basis-theory.js"></script>
|
|
75
|
+
<script>
|
|
76
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
77
|
+
const bt = BasisTheory('pk_test_...');
|
|
78
|
+
|
|
79
|
+
const cardNumberEl = bt.createElement('cardNumber');
|
|
80
|
+
const expiryEl = bt.createElement('expiry');
|
|
81
|
+
const cvvEl = bt.createElement('cvv');
|
|
82
|
+
|
|
83
|
+
await Promise.all([
|
|
84
|
+
cardNumberEl.mount('#card-number'),
|
|
85
|
+
expiryEl.mount('#expiry'),
|
|
86
|
+
cvvEl.mount('#cvv'),
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
171
90
|
```
|
|
172
91
|
|
|
173
|
-
|
|
92
|
+
## Element Types
|
|
174
93
|
|
|
175
|
-
|
|
94
|
+
| Type | `createElement` key | Description |
|
|
95
|
+
|------|---------------------|-------------|
|
|
96
|
+
| Card Number | `'cardNumber'` | PAN input with Luhn validation and brand detection |
|
|
97
|
+
| Expiry | `'expiry'` | MM/YY expiration date input |
|
|
98
|
+
| CVV | `'cvv'` | Card verification code input |
|
|
99
|
+
| Text | `'text'` | General-purpose secure text input (SSN, routing numbers, etc.) |
|
|
176
100
|
|
|
177
|
-
###
|
|
101
|
+
### Shared Options
|
|
178
102
|
|
|
179
|
-
|
|
180
|
-
const cardNumber = bt.createElement('cardNumber', {
|
|
181
|
-
placeholder: '4111 1111 1111 1111',
|
|
182
|
-
style: {
|
|
183
|
-
/* ... */
|
|
184
|
-
},
|
|
185
|
-
ariaLabel: 'Card Number',
|
|
186
|
-
disabled: false,
|
|
187
|
-
readOnly: false,
|
|
188
|
-
});
|
|
103
|
+
All element types accept these options in `createElement()` and `element.update()`:
|
|
189
104
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
-
```
|
|
105
|
+
| Option | Type | Default | Description |
|
|
106
|
+
|--------|------|---------|-------------|
|
|
107
|
+
| `placeholder` | `string` | -- | Placeholder text shown when the input is empty |
|
|
108
|
+
| `ariaLabel` | `string` | Element-specific | ARIA label for screen readers |
|
|
109
|
+
| `disabled` | `boolean` | `false` | Disables the input |
|
|
110
|
+
| `readOnly` | `boolean` | `false` | Makes the input read-only |
|
|
198
111
|
|
|
199
|
-
###
|
|
112
|
+
### Text Element Options
|
|
200
113
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
114
|
+
| Option | Type | Description |
|
|
115
|
+
|--------|------|-------------|
|
|
116
|
+
| `validation` | `RegExp` | Pattern the value must match to be valid |
|
|
117
|
+
| `required` | `boolean` | Whether the field is required |
|
|
118
|
+
| `maxLength` | `number` | Maximum character length |
|
|
119
|
+
| `password` | `boolean` | Renders as a password field |
|
|
120
|
+
| `inputMode` | `string` | Mobile keyboard hint (`'numeric'`, `'tel'`, etc.) |
|
|
121
|
+
| `mask` | `(RegExp \| string)[]` | Character-by-character input mask |
|
|
122
|
+
| `transform` | `[RegExp, string]` | Transform applied before tokenization |
|
|
208
123
|
|
|
209
|
-
|
|
210
|
-
cvv.on('change', (event) => {
|
|
211
|
-
console.log(event.detail.isValid);
|
|
212
|
-
console.log(event.detail.isComplete);
|
|
213
|
-
});
|
|
214
|
-
```
|
|
124
|
+
### CVV Element Options
|
|
215
125
|
|
|
216
|
-
|
|
126
|
+
| Option | Type | Default | Description |
|
|
127
|
+
|--------|------|---------|-------------|
|
|
128
|
+
| `showToggle` | `boolean` | `false` | Renders a show/hide toggle button inside the field |
|
|
217
129
|
|
|
218
|
-
|
|
219
|
-
const expiry = bt.createElement('expiry', {
|
|
220
|
-
placeholder: 'MM/YY',
|
|
221
|
-
style: {
|
|
222
|
-
/* ... */
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Events
|
|
227
|
-
expiry.on('change', (event) => {
|
|
228
|
-
console.log(event.detail.isValid);
|
|
229
|
-
console.log(event.detail.isComplete);
|
|
230
|
-
});
|
|
231
|
-
```
|
|
130
|
+
## Element Methods
|
|
232
131
|
|
|
233
|
-
|
|
132
|
+
Every element exposes the same interface:
|
|
234
133
|
|
|
235
|
-
|
|
134
|
+
| Method | Signature | Description |
|
|
135
|
+
|--------|-----------|-------------|
|
|
136
|
+
| `mount` | `(selector: string \| HTMLElement) => Promise<void>` | Attaches the iframe to the DOM |
|
|
137
|
+
| `unmount` | `() => void` | Removes the iframe from the DOM |
|
|
138
|
+
| `update` | `(options) => Promise<void>` | Updates options on a mounted element |
|
|
139
|
+
| `focus` | `() => void` | Focuses the input |
|
|
140
|
+
| `blur` | `() => void` | Blurs the input |
|
|
141
|
+
| `clear` | `() => void` | Clears the current value |
|
|
142
|
+
| `on` | `(event, listener) => () => void` | Subscribes to an event; returns an unsubscribe function |
|
|
236
143
|
|
|
237
|
-
|
|
144
|
+
Properties: `id` (string), `type` (ElementType), `mounted` (boolean).
|
|
238
145
|
|
|
239
|
-
|
|
240
|
-
// Mount to DOM
|
|
241
|
-
await element.mount('#container');
|
|
242
|
-
await element.mount(document.querySelector('#container'));
|
|
146
|
+
## Events
|
|
243
147
|
|
|
244
|
-
|
|
245
|
-
|
|
148
|
+
| Event | Fires when |
|
|
149
|
+
|-------|-----------|
|
|
150
|
+
| `ready` | Element iframe is loaded and interactive |
|
|
151
|
+
| `change` | Input value changes |
|
|
152
|
+
| `focus` | Element receives focus |
|
|
153
|
+
| `blur` | Element loses focus |
|
|
154
|
+
| `error` | An infrastructure or API error occurs |
|
|
246
155
|
|
|
247
|
-
|
|
248
|
-
await element.update({
|
|
249
|
-
placeholder: 'New placeholder',
|
|
250
|
-
style: { base: { color: '#333' } },
|
|
251
|
-
});
|
|
156
|
+
### change Event Payload
|
|
252
157
|
|
|
253
|
-
|
|
254
|
-
element.focus();
|
|
255
|
-
element.blur();
|
|
158
|
+
All elements include these fields in `event.detail`:
|
|
256
159
|
|
|
257
|
-
|
|
258
|
-
|
|
160
|
+
| Field | Type | Description |
|
|
161
|
+
|-------|------|-------------|
|
|
162
|
+
| `isValid` | `boolean` | Input passes all validation rules |
|
|
163
|
+
| `isEmpty` | `boolean` | Input is empty |
|
|
164
|
+
| `error` | `ValidationError \| null` | Validation error, or `null` if valid |
|
|
165
|
+
| `elementType` | `string` | Element type that fired the event |
|
|
166
|
+
| `elementId` | `string` | Unique element ID |
|
|
167
|
+
| `timestamp` | `number` | Unix timestamp (ms) |
|
|
259
168
|
|
|
260
|
-
|
|
261
|
-
const unsubscribe = element.on('change', (event) => {
|
|
262
|
-
console.log(event.detail);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Cleanup
|
|
266
|
-
unsubscribe();
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
## 🎭 **Events**
|
|
272
|
-
|
|
273
|
-
### **change**
|
|
274
|
-
|
|
275
|
-
Fired when the input value changes.
|
|
169
|
+
The `cardNumber` element adds: `cardBrand`, `last4`, `bin`, `cvvLengths`, `potentialBrands`, and `matchStrength`.
|
|
276
170
|
|
|
277
171
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
isEmpty: event.detail.isEmpty, // true/false
|
|
282
|
-
isComplete: event.detail.isComplete, // true/false
|
|
283
|
-
isPotentiallyValid: event.detail.isPotentiallyValid, // true/false (real-time UX)
|
|
284
|
-
cardBrand: event.detail.cardBrand, // 'visa' | 'mastercard' | ...
|
|
285
|
-
potentialBrands: event.detail.potentialBrands, // ['visa', 'mastercard']
|
|
286
|
-
error: event.detail.error, // { code, message }
|
|
287
|
-
});
|
|
172
|
+
cardNumberEl.on('change', (event) => {
|
|
173
|
+
const { isValid, cardBrand, last4 } = event.detail;
|
|
174
|
+
console.log(`${cardBrand} ...${last4}`, isValid);
|
|
288
175
|
});
|
|
289
176
|
```
|
|
290
177
|
|
|
291
|
-
###
|
|
292
|
-
|
|
293
|
-
Fired when the element is fully initialized and ready for user input.
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
element.on('ready', (event) => {
|
|
297
|
-
console.log('Element ready:', event.detail.elementType);
|
|
298
|
-
});
|
|
299
|
-
```
|
|
178
|
+
### error Event Codes
|
|
300
179
|
|
|
301
|
-
|
|
180
|
+
| Code | Description |
|
|
181
|
+
|------|-------------|
|
|
182
|
+
| `MOUNT_ERROR` | Failed to mount the element iframe |
|
|
183
|
+
| `POSTMESSAGE_TIMEOUT` | PostMessage timeout |
|
|
184
|
+
| `IFRAME_LOAD_ERROR` | Element iframe failed to load |
|
|
185
|
+
| `INITIALIZATION_ERROR` | SDK or coordinator failed to initialize |
|
|
186
|
+
| `API_ERROR` | Basis Theory API returned an error |
|
|
187
|
+
| `NETWORK_ERROR` | Network request failed |
|
|
188
|
+
| `VALIDATION_ERROR` | Input failed client-side validation |
|
|
189
|
+
| `INVALID_CONFIGURATION` | Invalid SDK or element options |
|
|
190
|
+
| `UNKNOWN_ERROR` | Unexpected error |
|
|
302
191
|
|
|
303
|
-
|
|
192
|
+
## SDK Options
|
|
304
193
|
|
|
305
194
|
```typescript
|
|
306
|
-
|
|
307
|
-
|
|
195
|
+
const bt = BasisTheory('pk_test_...', {
|
|
196
|
+
debug: false, // Enable verbose debug logging
|
|
197
|
+
themeMode: 'auto', // 'light' | 'dark' | 'auto'
|
|
198
|
+
theme: { /* ThemeTokens */ },
|
|
199
|
+
darkTheme: { /* ThemeTokens */ },
|
|
200
|
+
timeoutMs: 30000, // PostMessage timeout (ms)
|
|
308
201
|
});
|
|
309
202
|
```
|
|
310
203
|
|
|
311
|
-
|
|
204
|
+
## Services
|
|
312
205
|
|
|
313
|
-
|
|
206
|
+
### tokens
|
|
314
207
|
|
|
315
208
|
```typescript
|
|
316
|
-
|
|
317
|
-
|
|
209
|
+
// Create a token
|
|
210
|
+
const token = await bt.tokens.create({
|
|
211
|
+
type: 'card',
|
|
212
|
+
data: {
|
|
213
|
+
number: cardNumberEl,
|
|
214
|
+
expiration_month: expiryEl,
|
|
215
|
+
expiration_year: expiryEl,
|
|
216
|
+
cvc: cvvEl,
|
|
217
|
+
},
|
|
318
218
|
});
|
|
319
|
-
```
|
|
320
219
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
element.on('error', (event) => {
|
|
327
|
-
console.error('Element error:', {
|
|
328
|
-
code: event.detail.code,
|
|
329
|
-
message: event.detail.message,
|
|
330
|
-
details: event.detail.details,
|
|
331
|
-
});
|
|
220
|
+
// Retrieve a token
|
|
221
|
+
const retrieved = await bt.tokens.retrieve(token.id, {
|
|
222
|
+
apiKey: sessionApiKey, // from an authorized session
|
|
332
223
|
});
|
|
333
|
-
```
|
|
334
224
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
### **Single Element**
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
const token = await bt.tokenize({
|
|
343
|
-
cardNumber: cardNumber,
|
|
225
|
+
// Update a token
|
|
226
|
+
await bt.tokens.update(token.id, {
|
|
227
|
+
metadata: { source: 'updated' },
|
|
344
228
|
});
|
|
345
|
-
```
|
|
346
229
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
expiry: expiry,
|
|
354
|
-
});
|
|
230
|
+
// Encrypt with client-side JWE
|
|
231
|
+
const encrypted = await bt.tokens.encrypt(
|
|
232
|
+
{ type: 'card', data: { number: cardNumberEl, cvc: cvvEl } },
|
|
233
|
+
publicKeyPEM,
|
|
234
|
+
'key-id-123'
|
|
235
|
+
);
|
|
355
236
|
```
|
|
356
237
|
|
|
357
|
-
###
|
|
238
|
+
### tokenize
|
|
358
239
|
|
|
359
240
|
```typescript
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
brand?: string; // Card brand (e.g., "visa")
|
|
365
|
-
expiry?: {
|
|
366
|
-
month: number;
|
|
367
|
-
year: number;
|
|
368
|
-
};
|
|
369
|
-
metadata?: Record<string, unknown>;
|
|
370
|
-
}
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
## 🎨 **Styling**
|
|
376
|
-
|
|
377
|
-
### **Style States**
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
interface ElementStyle {
|
|
381
|
-
base?: CSSProperties; // Default state
|
|
382
|
-
invalid?: CSSProperties; // When input is invalid
|
|
383
|
-
complete?: CSSProperties; // When input is valid and complete
|
|
384
|
-
empty?: CSSProperties; // When input is empty
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### **Supported CSS Properties**
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
interface CSSProperties {
|
|
392
|
-
color?: string;
|
|
393
|
-
backgroundColor?: string;
|
|
394
|
-
fontSize?: string;
|
|
395
|
-
fontFamily?: string;
|
|
396
|
-
fontWeight?: string;
|
|
397
|
-
lineHeight?: string;
|
|
398
|
-
letterSpacing?: string;
|
|
399
|
-
textAlign?: string;
|
|
400
|
-
padding?: string;
|
|
401
|
-
margin?: string;
|
|
402
|
-
border?: string;
|
|
403
|
-
borderRadius?: string;
|
|
404
|
-
boxShadow?: string;
|
|
405
|
-
// ... more (see API Reference section below for complete list)
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### **Example**
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
const cardNumber = bt.createElement('cardNumber', {
|
|
413
|
-
style: {
|
|
414
|
-
base: {
|
|
415
|
-
color: '#000',
|
|
416
|
-
fontSize: '16px',
|
|
417
|
-
fontFamily: 'Arial, sans-serif',
|
|
418
|
-
backgroundColor: '#fff',
|
|
419
|
-
padding: '12px',
|
|
420
|
-
border: '1px solid #ddd',
|
|
421
|
-
borderRadius: '4px',
|
|
422
|
-
},
|
|
423
|
-
invalid: {
|
|
424
|
-
color: '#EA4B97',
|
|
425
|
-
borderColor: '#EA4B97',
|
|
426
|
-
},
|
|
427
|
-
complete: {
|
|
428
|
-
color: '#1976d2',
|
|
429
|
-
borderColor: '#1976d2',
|
|
430
|
-
},
|
|
431
|
-
empty: {
|
|
432
|
-
color: '#999',
|
|
433
|
-
},
|
|
434
|
-
},
|
|
241
|
+
const result = await bt.tokenize({
|
|
242
|
+
number: cardNumberEl,
|
|
243
|
+
cvv: cvvEl,
|
|
244
|
+
expiry: expiryEl,
|
|
435
245
|
});
|
|
436
246
|
```
|
|
437
247
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
## ⚙️ **Configuration**
|
|
441
|
-
|
|
442
|
-
### **SDK Options**
|
|
248
|
+
### tokenIntents
|
|
443
249
|
|
|
444
250
|
```typescript
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
delayMs: 1000,
|
|
453
|
-
backoffFactor: 2,
|
|
251
|
+
const intent = await bt.tokenIntents.create({
|
|
252
|
+
type: 'card',
|
|
253
|
+
data: {
|
|
254
|
+
number: cardNumberEl,
|
|
255
|
+
expiration_month: expiryEl,
|
|
256
|
+
expiration_year: expiryEl,
|
|
257
|
+
cvc: cvvEl,
|
|
454
258
|
},
|
|
455
|
-
themeMode: 'auto', // 'light' | 'dark' | 'auto'
|
|
456
|
-
theme: { /* theme tokens */ },
|
|
457
|
-
darkTheme: { /* dark theme tokens */ },
|
|
458
259
|
});
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### **Element Options**
|
|
462
260
|
|
|
463
|
-
|
|
464
|
-
interface ElementOptions {
|
|
465
|
-
placeholder?: string; // Placeholder text
|
|
466
|
-
style?: ElementStyle; // Styling for different states
|
|
467
|
-
ariaLabel?: string; // ARIA label for accessibility
|
|
468
|
-
disabled?: boolean; // Disable input
|
|
469
|
-
readOnly?: boolean; // Make input read-only
|
|
470
|
-
}
|
|
261
|
+
const retrieved = await bt.tokenIntents.get(intent.id);
|
|
471
262
|
```
|
|
472
263
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
## 🧪 **Testing**
|
|
476
|
-
|
|
477
|
-
### **Unit Testing**
|
|
264
|
+
### sessions
|
|
478
265
|
|
|
479
266
|
```typescript
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
test('should create and mount element', async () => {
|
|
484
|
-
const { container } = render(<div id="card-number" />);
|
|
485
|
-
|
|
486
|
-
const bt = BasisTheory('pk_test_123');
|
|
487
|
-
const cardNumber = bt.createElement('cardNumber');
|
|
267
|
+
const session = await bt.sessions.create();
|
|
268
|
+
// Send session.nonce to your backend to authorize
|
|
269
|
+
// Backend returns an apiKey for elevated access
|
|
488
270
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
expect(cardNumber.mounted).toBe(true);
|
|
492
|
-
expect(container.querySelector('iframe')).toBeInTheDocument();
|
|
271
|
+
const token = await bt.tokens.retrieve(tokenId, {
|
|
272
|
+
apiKey: sessionApiKey,
|
|
493
273
|
});
|
|
494
274
|
```
|
|
495
275
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
cy.visit('/payment-form.html');
|
|
500
|
-
|
|
501
|
-
cy.window().then((win) => {
|
|
502
|
-
const bt = win.BasisTheory('pk_test_123');
|
|
503
|
-
const cardNumber = bt.createElement('cardNumber');
|
|
504
|
-
|
|
505
|
-
return cardNumber.mount('#card-number');
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
cy.get('iframe').should('exist');
|
|
509
|
-
cy.iframe().find('input').type('4111111111111111');
|
|
510
|
-
cy.contains('Card Brand: visa');
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
---
|
|
514
|
-
|
|
515
|
-
## 🔒 **Security**
|
|
516
|
-
|
|
517
|
-
### **PCI-DSS Compliance**
|
|
518
|
-
|
|
519
|
-
✅ **Stateless Elements** - No sensitive data persisted in SDK
|
|
520
|
-
✅ **Iframe Isolation** - Payment inputs sandboxed in secure iframes
|
|
521
|
-
✅ **Origin Validation** - PostMessage communication validates sender origin
|
|
522
|
-
✅ **No Direct PAN Access** - SDK never touches raw card numbers
|
|
523
|
-
✅ **Secure Tokenization** - Values retrieved ephemerally from iframe
|
|
524
|
-
|
|
525
|
-
### **Best Practices**
|
|
526
|
-
|
|
527
|
-
- ✅ **Use HTTPS** in production
|
|
528
|
-
- ✅ **Validate API key** is for correct environment (test vs production)
|
|
529
|
-
- ✅ **Don't log sensitive data** (card numbers, CVVs, etc.)
|
|
530
|
-
- ✅ **Implement CSP headers** to prevent XSS
|
|
531
|
-
- ✅ **Use subresource integrity** for CDN-hosted iframes
|
|
532
|
-
|
|
533
|
-
**See [Security Architecture](../../docs/user-guide/security.md) for detailed security measures.**
|
|
534
|
-
|
|
535
|
-
---
|
|
536
|
-
|
|
537
|
-
## 🏗️ **Architecture**
|
|
538
|
-
|
|
539
|
-
The v3 SDK is built with **composition over inheritance**:
|
|
540
|
-
|
|
541
|
-
```
|
|
542
|
-
@basis-theory-elements/sdk # Public API
|
|
543
|
-
├── BasisTheory.ts # Main SDK factory
|
|
544
|
-
├── elements/
|
|
545
|
-
│ ├── CardNumberElement.ts # Card number element
|
|
546
|
-
│ ├── CVVElement.ts # CVV element
|
|
547
|
-
│ └── ExpiryElement.ts # Expiry date element
|
|
548
|
-
├── mounter/
|
|
549
|
-
│ └── ElementMounter.ts # DOM mounting logic
|
|
550
|
-
└── utils/
|
|
551
|
-
├── iframe.ts # Iframe creation utilities
|
|
552
|
-
└── config.ts # Configuration validation
|
|
553
|
-
|
|
554
|
-
@basis-theory-elements/postmessage # Communication layer
|
|
555
|
-
├── client.ts # PostMessage client (parent side)
|
|
556
|
-
├── server.ts # PostMessage server (iframe side)
|
|
557
|
-
└── types.ts # Message type definitions
|
|
558
|
-
|
|
559
|
-
@basis-theory-elements/validation # Validation logic
|
|
560
|
-
├── validators/
|
|
561
|
-
│ ├── card-number.ts # Card number validation
|
|
562
|
-
│ ├── cvv.ts # CVV validation
|
|
563
|
-
│ └── expiry.ts # Expiry date validation
|
|
564
|
-
└── types.ts # Validation type definitions
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
**Benefits:**
|
|
568
|
-
|
|
569
|
-
- ✅ **Single Responsibility** - Each module does one thing well
|
|
570
|
-
- ✅ **Independently Testable** - Test each module in isolation
|
|
571
|
-
- ✅ **Reusable** - Other applications can use `@postmessage` or `@validation` without SDK
|
|
572
|
-
- ✅ **Clear Dependencies** - No circular references, clean module boundaries
|
|
573
|
-
|
|
574
|
-
**See [Coordinator Architecture](../../docs/architecture/coordinator.md) for detailed implementation.**
|
|
575
|
-
|
|
576
|
-
---
|
|
577
|
-
|
|
578
|
-
## 📚 **Documentation**
|
|
579
|
-
|
|
580
|
-
- [Quick Start](./README.md) - This document
|
|
581
|
-
- [v3 Documentation](../../docs/README.md) - Complete architecture and implementation details
|
|
582
|
-
- [v2 to v3 Migration](../../docs/user-guide/migration.md) - Migration guide and improvements
|
|
583
|
-
|
|
584
|
-
**Related Packages:**
|
|
585
|
-
|
|
586
|
-
- [PostMessage Module](../postmessage/README.md) - Cross-iframe communication
|
|
587
|
-
- [Validation Module](../validation/README.md) - Payment data validation
|
|
588
|
-
|
|
589
|
-
---
|
|
276
|
+
## Theming
|
|
590
277
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
The v2 SDK (`@basis-theory/basis-theory-js`) has **accidental complexity**:
|
|
594
|
-
|
|
595
|
-
1. ❌ **Dynamic script loading** - CDN dependency, hard to test
|
|
596
|
-
2. ❌ **Global namespace pollution** - `window.BasisTheoryElements`
|
|
597
|
-
3. ❌ **RxJS dependency** - 35KB+ for simple pub-sub
|
|
598
|
-
4. ❌ **1147-line god class** - `BaseElement` does everything
|
|
599
|
-
5. ❌ **DataElement anti-pattern** - Hidden coordinator iframe
|
|
600
|
-
6. ❌ **Complex initialization** - 10+ async steps to initialize
|
|
601
|
-
7. ❌ **Over-abstraction** - Too many layers for PostMessage
|
|
602
|
-
|
|
603
|
-
**v3 fixes all of these:**
|
|
604
|
-
|
|
605
|
-
- ✅ **Direct ESM import** - no dynamic loading
|
|
606
|
-
- ✅ **Zero global pollution** - standard module imports
|
|
607
|
-
- ✅ **Zero RxJS dependency** - native `EventTarget`
|
|
608
|
-
- ✅ **Composition over inheritance** - focused modules
|
|
609
|
-
- ✅ **No hidden iframes** - SDK client handles coordination
|
|
610
|
-
- ✅ **Simple initialization** - one-step, synchronous
|
|
611
|
-
- ✅ **Clear separation of concerns** - mounting, messaging, validation are independent
|
|
612
|
-
|
|
613
|
-
**See [v2 to v3 Migration](../../docs/user-guide/migration.md) for detailed comparison.**
|
|
614
|
-
|
|
615
|
-
---
|
|
616
|
-
|
|
617
|
-
## 🐛 **Troubleshooting**
|
|
618
|
-
|
|
619
|
-
### **Element Not Mounting**
|
|
278
|
+
Pass design tokens at initialization and switch modes at runtime:
|
|
620
279
|
|
|
621
280
|
```typescript
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
281
|
+
const bt = BasisTheory('pk_test_...', {
|
|
282
|
+
themeMode: 'auto',
|
|
283
|
+
theme: {
|
|
284
|
+
colors: {
|
|
285
|
+
primary: '#007bff',
|
|
286
|
+
error: '#dc3545',
|
|
287
|
+
success: '#28a745',
|
|
288
|
+
text: { primary: '#1a1a1a', placeholder: '#6c757d' },
|
|
289
|
+
background: { default: '#ffffff' },
|
|
290
|
+
border: { default: '#dee2e6', focus: '#007bff' },
|
|
291
|
+
},
|
|
292
|
+
typography: {
|
|
293
|
+
fontFamily: 'Inter, sans-serif',
|
|
294
|
+
fontSize: { base: '16px' },
|
|
295
|
+
fontWeight: { normal: '400' },
|
|
296
|
+
},
|
|
297
|
+
spacing: { sm: '8px', md: '12px', lg: '16px' },
|
|
298
|
+
borders: {
|
|
299
|
+
radius: { base: '6px' },
|
|
300
|
+
width: { base: '1px' },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
631
303
|
});
|
|
632
304
|
|
|
633
|
-
//
|
|
634
|
-
|
|
635
|
-
debug: true, // See PostMessage logs in console
|
|
636
|
-
});
|
|
305
|
+
// Switch theme mode at runtime
|
|
306
|
+
await bt.updateThemeMode('dark');
|
|
637
307
|
```
|
|
638
308
|
|
|
639
|
-
|
|
309
|
+
## TypeScript
|
|
640
310
|
|
|
641
|
-
|
|
642
|
-
// Increase timeout for slow networks
|
|
643
|
-
const bt = BasisTheory('pk_test_123', {
|
|
644
|
-
timeoutMs: 10000, // 10 seconds
|
|
645
|
-
});
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
### **CORS Errors**
|
|
311
|
+
The package ships full type definitions. Key exports:
|
|
649
312
|
|
|
650
313
|
```typescript
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
314
|
+
import type {
|
|
315
|
+
BasisTheorySDK,
|
|
316
|
+
Element,
|
|
317
|
+
ElementType,
|
|
318
|
+
ElementOptions,
|
|
319
|
+
SDKOptions,
|
|
320
|
+
ChangeEventDetail,
|
|
321
|
+
CardNumberChangeEventDetail,
|
|
322
|
+
ReadyEventDetail,
|
|
323
|
+
ErrorEventDetail,
|
|
324
|
+
ErrorCode,
|
|
325
|
+
ValidationError,
|
|
326
|
+
TokenizeResult,
|
|
327
|
+
Session,
|
|
328
|
+
} from '@basis-theory/web-elements';
|
|
655
329
|
```
|
|
656
330
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
## 📄 **License**
|
|
331
|
+
## Documentation
|
|
660
332
|
|
|
661
|
-
|
|
333
|
+
Full documentation: [https://developers.basistheory.com/docs/sdks/web/web-elements/](https://developers.basistheory.com/docs/sdks/web/web-elements/)
|
|
662
334
|
|
|
663
|
-
|
|
335
|
+
- [Getting Started](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/getting-started)
|
|
336
|
+
- [Element Types](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components)
|
|
337
|
+
- [Events](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/events)
|
|
338
|
+
- [Services](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/services)
|
|
339
|
+
- [Theming](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/theming)
|
|
340
|
+
- [Migration Guide (v2 to v3)](https://developers.basistheory.com/docs/sdks/web/web-elements/v3/migration)
|
|
664
341
|
|
|
665
|
-
##
|
|
342
|
+
## License
|
|
666
343
|
|
|
667
|
-
-
|
|
668
|
-
- 🐛 **Issues**: [GitHub Issues](https://github.com/basis-theory/elements/issues)
|
|
669
|
-
- 💬 **Community**: [Slack](https://community.basistheory.com)
|
|
344
|
+
Apache-2.0
|