@rinnebr/js 0.1.0-alpha.1

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 ADDED
@@ -0,0 +1,549 @@
1
+ # Rinne JS
2
+
3
+ A modern, type-safe browser library for integrating Apple Pay and Google Pay payment elements into your web applications. Built on Rinne's secure payment infrastructure with encryption powered by Evervault.
4
+
5
+ ## Overview
6
+
7
+ Rinne JS provides a simple, declarative API for rendering wallet payment buttons in the browser. The library handles:
8
+
9
+ - Secure payment token encryption and transmission
10
+ - Wallet button rendering and styling
11
+ - Payment flow orchestration
12
+ - Error handling and user cancellation
13
+ - Transaction management
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install rinne-js
19
+ # or
20
+ yarn add rinne-js
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { Rinne } from 'rinne-js'
27
+
28
+ // 1. Initialize Rinne with your merchant ID
29
+ const rinne = new Rinne({
30
+ merchantId: 'your-merchant-id',
31
+ environment: 'production' // or 'sandbox'
32
+ })
33
+
34
+ // 2. Create a transaction
35
+ const transaction = await rinne.transaction.create({
36
+ amount: 1000, // Amount in cents
37
+ currency: 'BRL',
38
+ country: 'BR',
39
+ lineItems: [
40
+ { label: 'Product Name', amount: 1000 }
41
+ ]
42
+ })
43
+
44
+ // 3. Create and mount an Apple Pay element
45
+ const applePay = await rinne.elements.applePay(transaction, {
46
+ button: {
47
+ color: 'black',
48
+ type: 'buy'
49
+ },
50
+ onCapture: async (payload, fail) => {
51
+ try {
52
+ // Send encrypted card data to your backend
53
+ await processPayment(payload.card_data, payload.transaction)
54
+ } catch (error) {
55
+ // Call fail() to notify the wallet dialog that payment processing failed
56
+ fail({ message: 'Payment processing failed' })
57
+ }
58
+ },
59
+ onError: (error) => {
60
+ console.error('Apple Pay error:', error)
61
+ },
62
+ onCancel: () => {
63
+ console.log('Payment cancelled by user')
64
+ }
65
+ })
66
+
67
+ await applePay.mount('#payment-button')
68
+ ```
69
+
70
+ ## Core Concepts
71
+
72
+ ### Initialization
73
+
74
+ The `Rinne` class is the main entry point. It handles SDK initialization, merchant configuration fetching, and Evervault setup.
75
+
76
+ ```typescript
77
+ const rinne = new Rinne({
78
+ merchantId: string // Required: Your Rinne merchant ID
79
+ environment?: Environment // Optional: 'sandbox' | 'production' (default: 'production')
80
+ rinneUrl?: string // Optional: Custom Rinne API base URL
81
+ })
82
+ ```
83
+
84
+ **Lazy Initialization**: The SDK initializes asynchronously when you first create a transaction or payment element. This includes fetching merchant configuration and loading the Evervault SDK.
85
+
86
+ ### Transactions
87
+
88
+ Every payment requires a transaction object that defines the payment amount, currency, and line items.
89
+
90
+ ```typescript
91
+ const transaction = await rinne.transaction.create({
92
+ amount: number // Required: Amount in cents (e.g., 1000 = $10.00)
93
+ currency: string // Required: ISO 4217 currency code (e.g., 'BRL', 'USD')
94
+ country?: string // Optional: ISO 3166 country code (e.g., 'BR', 'US')
95
+ lineItems?: Array<{ // Optional: Itemized list for wallet display
96
+ label: string // Line item description
97
+ amount: number // Line item amount in cents
98
+ }>
99
+ })
100
+ ```
101
+
102
+ Transaction objects are immutable and tied to a single payment attempt.
103
+
104
+ ### Payment Elements
105
+
106
+ Payment elements are wallet-specific UI components that handle the complete payment flow:
107
+
108
+ ```typescript
109
+ // Apple Pay
110
+ const applePay = await rinne.elements.applePay(transaction, options)
111
+ await applePay.mount('#container')
112
+
113
+ // Google Pay
114
+ const googlePay = await rinne.elements.googlePay(transaction, options)
115
+ await googlePay.mount('#container')
116
+ ```
117
+
118
+ ## API Reference
119
+
120
+ ### `Rinne`
121
+
122
+ #### Constructor
123
+
124
+ ```typescript
125
+ new Rinne(options: Options)
126
+ ```
127
+
128
+ #### Properties
129
+
130
+ - `transaction: TransactionNamespace` - Transaction management namespace
131
+ - `elements: ElementsNamespace` - Payment element creation namespace
132
+
133
+ ### `rinne.transaction`
134
+
135
+ #### `create(params: TransactionCreateParams): Promise<EvTransaction>`
136
+
137
+ Creates a new transaction for a payment.
138
+
139
+ **Parameters:**
140
+ - `amount: number` - Payment amount in cents
141
+ - `currency: string` - ISO 4217 currency code
142
+ - `country?: string` - ISO 3166 country code
143
+ - `lineItems?: WalletLineItem[]` - Itemized breakdown
144
+
145
+ **Returns:** Transaction object to be used with payment elements
146
+
147
+ **Example:**
148
+ ```typescript
149
+ const transaction = await rinne.transaction.create({
150
+ amount: 5000,
151
+ currency: 'BRL',
152
+ country: 'BR',
153
+ lineItems: [
154
+ { label: 'Subtotal', amount: 4500 },
155
+ { label: 'Shipping', amount: 500 }
156
+ ]
157
+ })
158
+ ```
159
+
160
+ ### `rinne.elements`
161
+
162
+ #### `applePay(transaction: EvTransaction, options: ApplePayMountOptions): Promise<ApplePayElement>`
163
+
164
+ Creates an Apple Pay payment element.
165
+
166
+ **Parameters:**
167
+ - `transaction` - Transaction object from `rinne.transaction.create()`
168
+ - `options` - Configuration and event handlers (see [Mount Options](#mount-options))
169
+
170
+ **Returns:** Apple Pay element with `mount()` method
171
+
172
+ #### `googlePay(transaction: EvTransaction, options: GooglePayMountOptions): Promise<GooglePayElement>`
173
+
174
+ Creates a Google Pay payment element.
175
+
176
+ **Parameters:**
177
+ - `transaction` - Transaction object from `rinne.transaction.create()`
178
+ - `options` - Configuration and event handlers (see [Mount Options](#mount-options))
179
+
180
+ **Returns:** Google Pay element with `mount()` method
181
+
182
+ ### Payment Element Methods
183
+
184
+ #### `mount(target: string | HTMLElement): Promise<MountedElement>`
185
+
186
+ Mounts the payment button to a DOM element.
187
+
188
+ **Parameters:**
189
+ - `target` - CSS selector string (e.g., `'#button'`) or HTMLElement
190
+
191
+ **Returns:** Mounted element with `unmount()` method
192
+
193
+ **Throws:** If target element is not found or mounting fails
194
+
195
+ **Example:**
196
+ ```typescript
197
+ const element = await rinne.elements.applePay(transaction, options)
198
+
199
+ // Mount using selector
200
+ await element.mount('#apple-pay-button')
201
+
202
+ // Or mount using element reference
203
+ const container = document.getElementById('payment-container')
204
+ await element.mount(container)
205
+ ```
206
+
207
+ #### `unmount(): void`
208
+
209
+ Removes the payment button from the DOM.
210
+
211
+ **Example:**
212
+ ```typescript
213
+ const mounted = await element.mount('#container')
214
+
215
+ // Later...
216
+ mounted.unmount()
217
+ ```
218
+
219
+ ## Configuration
220
+
221
+ ### Mount Options
222
+
223
+ Both `applePay()` and `googlePay()` accept the same configuration structure:
224
+
225
+ ```typescript
226
+ interface MountOptions {
227
+ button?: WalletButtonOptions
228
+ onCapture?: (payload: WalletSuccessPayload, fail: (error) => void) => void
229
+ onError?: (error: WalletError) => void
230
+ onCancel?: () => void
231
+ }
232
+ ```
233
+
234
+ ### Button Options
235
+
236
+ Customize the appearance of wallet buttons:
237
+
238
+ ```typescript
239
+ interface WalletButtonOptions {
240
+ color?: 'black' | 'white' // Default: 'black'
241
+ locale?: 'pt' | 'en' | 'es' // Default: 'pt'
242
+ type?: WalletButtonType // Default: 'plain'
243
+ borderRadius?: number // Default: platform-specific
244
+ size?: {
245
+ width: number | string // Default: '100%'
246
+ height: number | string // Default: '40px'
247
+ }
248
+ }
249
+ ```
250
+
251
+ **Button Types:**
252
+ - `'plain'` - Generic payment button
253
+ - `'buy'` - "Buy with [Wallet]"
254
+ - `'book'` - "Book with [Wallet]"
255
+ - `'checkout'` - "Check out with [Wallet]"
256
+ - `'donate'` - "Donate with [Wallet]"
257
+ - `'order'` - "Order with [Wallet]"
258
+ - `'pay'` - "Pay with [Wallet]"
259
+ - `'subscribe'` - "Subscribe with [Wallet]"
260
+
261
+ **Defaults:**
262
+ ```typescript
263
+ {
264
+ color: 'black',
265
+ locale: 'pt',
266
+ type: 'plain',
267
+ size: {
268
+ width: '100%',
269
+ height: '40px'
270
+ }
271
+ }
272
+ ```
273
+
274
+ **Example:**
275
+ ```typescript
276
+ const applePay = await rinne.elements.applePay(transaction, {
277
+ button: {
278
+ color: 'white',
279
+ locale: 'en',
280
+ type: 'buy',
281
+ borderRadius: 8,
282
+ size: {
283
+ width: '300px',
284
+ height: '48px'
285
+ }
286
+ }
287
+ })
288
+ ```
289
+
290
+ ## Event Handlers
291
+
292
+ ### `onCapture`
293
+
294
+ Called when the user successfully authorizes payment with their wallet. This is where you process the payment on your backend.
295
+
296
+ ```typescript
297
+ onCapture: (payload: WalletSuccessPayload, fail: (error) => void) => void
298
+ ```
299
+
300
+ **Parameters:**
301
+
302
+ 1. **`payload: WalletSuccessPayload`**
303
+ ```typescript
304
+ {
305
+ card_data: CardData // Encrypted payment token and card details
306
+ transaction: EvTransaction // Original transaction object
307
+ }
308
+ ```
309
+
310
+ **CardData structure:**
311
+ ```typescript
312
+ {
313
+ cryptogram: string // Payment cryptogram for authorization
314
+ expiry_month: string // Card expiry month
315
+ expiry_year: string // Card expiry year
316
+ wallet_type: 'APPLE_PAY' | 'GOOGLE_PAY'
317
+ last_digits?: string // Last 4 digits of card
318
+ brand?: string // Card brand (Visa, Mastercard, etc.)
319
+ network_token?: string // Network tokenized PAN
320
+ device_id?: string // Device identifier
321
+ eci?: string // E-commerce indicator
322
+ // ... additional fields
323
+ }
324
+ ```
325
+
326
+ 2. **`fail: (error: { message: string }) => void`**
327
+
328
+ **CRITICAL**: This callback must be called if payment processing fails on your backend. It notifies the wallet dialog to show an error state to the user and allows them to retry or cancel.
329
+
330
+ If you don't call `fail()` after a processing error, the wallet dialog might appear stuck in a processing state or incorrectly report success.
331
+
332
+ **Example:**
333
+ ```typescript
334
+ onCapture: async (payload, fail) => {
335
+ try {
336
+ // Send encrypted card data to your backend
337
+ const response = await fetch('/api/process-payment', {
338
+ method: 'POST',
339
+ headers: { 'Content-Type': 'application/json' },
340
+ body: JSON.stringify({
341
+ cardData: payload.card_data,
342
+ transactionId: payload.transaction.id
343
+ })
344
+ })
345
+
346
+ if (!response.ok) {
347
+ throw new Error('Payment processing failed')
348
+ }
349
+
350
+ // Payment succeeded - wallet dialog will close automatically
351
+ const result = await response.json()
352
+ console.log('Payment successful:', result)
353
+
354
+ } catch (error) {
355
+ // Payment failed - MUST call fail() to notify the wallet
356
+ fail({
357
+ message: error.message || 'Unable to process payment'
358
+ })
359
+ }
360
+ }
361
+ ```
362
+
363
+ ### `onError`
364
+
365
+ Called when an error occurs during the wallet handling (e.g., wallet unavailable, network error, configuration error).
366
+
367
+ ```typescript
368
+ onError: (error: WalletError) => void
369
+ ```
370
+
371
+ **Error structure:**
372
+ ```typescript
373
+ {
374
+ code: 'PROVIDER_ERROR' | 'VALIDATION_ERROR' | 'CONFIGURATION_ERROR' | 'UNKNOWN'
375
+ message: string
376
+ details?: unknown
377
+ }
378
+ ```
379
+
380
+ **Error codes:**
381
+ - `PROVIDER_ERROR` - Error from Apple Pay or Google Pay provider
382
+ - `VALIDATION_ERROR` - Invalid configuration or parameters
383
+ - `CONFIGURATION_ERROR` - Merchant configuration issue
384
+ - `UNKNOWN` - Unexpected error
385
+
386
+ **Example:**
387
+ ```typescript
388
+ onError: (error) => {
389
+ console.error('Payment error:', error)
390
+
391
+ if (error.code === 'PROVIDER_ERROR') {
392
+ alert('Payment service unavailable. Please try another payment method.')
393
+ }
394
+ }
395
+ ```
396
+
397
+ ### `onCancel`
398
+
399
+ Called when the user explicitly cancels the payment (closes the wallet dialog without authorizing).
400
+
401
+ ```typescript
402
+ onCancel: () => void
403
+ ```
404
+
405
+ **Example:**
406
+ ```typescript
407
+ onCancel: () => {
408
+ console.log('User cancelled payment')
409
+ // Optionally re-enable other UI elements
410
+ }
411
+ ```
412
+
413
+ ## Complete Example
414
+
415
+ ```typescript
416
+ import { Rinne } from 'rinne-js'
417
+
418
+ const rinne = new Rinne({
419
+ merchantId: 'merchant_abc123',
420
+ environment: 'production'
421
+ })
422
+
423
+ async function setupPayment() {
424
+ // Create transaction
425
+ const transaction = await rinne.transaction.create({
426
+ amount: 2500, // $25.00
427
+ currency: 'USD',
428
+ country: 'US',
429
+ lineItems: [
430
+ { label: 'Premium Subscription', amount: 2000 },
431
+ { label: 'Tax', amount: 500 }
432
+ ]
433
+ })
434
+
435
+ // Setup Apple Pay
436
+ const applePay = await rinne.elements.applePay(transaction, {
437
+ button: {
438
+ color: 'black',
439
+ type: 'subscribe',
440
+ locale: 'en',
441
+ size: {
442
+ width: '100%',
443
+ height: '48px'
444
+ }
445
+ },
446
+ onCapture: async (payload, fail) => {
447
+ try {
448
+ const response = await fetch('/api/payments', {
449
+ method: 'POST',
450
+ headers: { 'Content-Type': 'application/json' },
451
+ body: JSON.stringify({
452
+ cardData: payload.card_data,
453
+ transactionId: payload.transaction.id
454
+ })
455
+ })
456
+
457
+ if (!response.ok) {
458
+ const error = await response.json()
459
+ throw new Error(error.message)
460
+ }
461
+
462
+ // Payment successful
463
+ window.location.href = '/success'
464
+
465
+ } catch (error) {
466
+ // Notify wallet of failure
467
+ fail({ message: error.message })
468
+
469
+ // Show error to user
470
+ alert('Payment failed: ' + error.message)
471
+ }
472
+ },
473
+ onError: (error) => {
474
+ console.error('Payment error:', error)
475
+ alert('Payment system error. Please try again.')
476
+ },
477
+ onCancel: () => {
478
+ console.log('Payment cancelled')
479
+ }
480
+ })
481
+
482
+ await applePay.mount('#apple-pay-button')
483
+
484
+ // Setup Google Pay
485
+ const googlePay = await rinne.elements.googlePay(transaction, {
486
+ button: {
487
+ color: 'black',
488
+ type: 'subscribe',
489
+ locale: 'en'
490
+ },
491
+ onCapture: async (payload, fail) => {
492
+ // Same handler as Apple Pay
493
+ try {
494
+ await processPayment(payload)
495
+ } catch (error) {
496
+ fail({ message: error.message })
497
+ }
498
+ },
499
+ onError: (error) => console.error(error),
500
+ onCancel: () => console.log('Cancelled')
501
+ })
502
+
503
+ await googlePay.mount('#google-pay-button')
504
+ }
505
+
506
+ setupPayment()
507
+ ```
508
+
509
+ ## TypeScript Support
510
+
511
+ Rinne JS is written in TypeScript and exports all type definitions:
512
+
513
+ ```typescript
514
+ import type {
515
+ Options,
516
+ Environment,
517
+ TransactionCreateParams,
518
+ WalletSuccessPayload,
519
+ WalletError,
520
+ WalletButtonOptions,
521
+ CardData,
522
+ ApplePayElement,
523
+ GooglePayElement,
524
+ MountedElement
525
+ } from 'rinne-js'
526
+ ```
527
+
528
+ ## Error Handling Best Practices
529
+
530
+ 1. **Always handle `onCapture` failures**: Call `fail()` with a useful message if backend processing fails
531
+ 2. **Implement `onError`**: Handle what to do in the rare event that the wallet buttons fail to load
532
+ 3. **Handle `onCancel`**: Clean up UI state when user cancels
533
+ 4. **Network errors**: Wrap backend calls in try-catch
534
+ 5. **Validation**: Validate transaction parameters before calling `transaction.create()`
535
+
536
+ ## Security
537
+
538
+ - All payment tokens are encrypted using Evervault before transmission
539
+ - No sensitive card data is stored in browser memory
540
+ - Source maps are disabled to protect payment flow logic
541
+ - Strict Content Security Policy recommended
542
+
543
+ ## Development
544
+
545
+ See [CLAUDE.md](./CLAUDE.md) for development setup and tooling information.
546
+
547
+ ## License
548
+
549
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let e=null;async function o(){if(window.Evervault)return window.Evervault;try{return await async function(){return e||(e=new Promise((o,r)=>{const t=document.createElement("script");if(t.src="https://js.evervault.com/v2",t.onload=()=>o(),t.onerror=()=>{e=null,r()},!document.head)throw new Error("Expected document.head not to be null. Evervault.js requires a <head> element.");document.head.appendChild(t)}),e)}(),window.Evervault}catch{throw new Error("Failed to load Evervault.js")}}Promise.resolve().then(()=>{o()});const r={pt:{errors:{targetElementNotFound:"Elemento alvo não encontrado",failedToInitialize:"Falha ao inicializar Rinne SDK",failedToProcessApplePay:"Falha ao processar dados do Apple Pay",failedToProcessGooglePay:"Falha ao processar dados do Google Pay",applePayError:"Erro do Apple Pay",googlePayError:"Erro do Google Pay",failedToMountApplePay:"Falha ao montar botão do Apple Pay",failedToMountGooglePay:"Falha ao montar botão do Google Pay",failedToFetchConfig:"Falha ao buscar configuração do comerciante",invalidConfig:"Configuração de comerciante inválida: campos obrigatórios ausentes",configFetchFailed:"Falha ao buscar configuração",unknownError:"Erro desconhecido"}},en:{errors:{targetElementNotFound:"Target element not found",failedToInitialize:"Failed to initialize Rinne SDK",failedToProcessApplePay:"Failed to process Apple Pay data",failedToProcessGooglePay:"Failed to process Google Pay data",applePayError:"Apple Pay error occurred",googlePayError:"Google Pay error occurred",failedToMountApplePay:"Failed to mount Apple Pay button",failedToMountGooglePay:"Failed to mount Google Pay button",failedToFetchConfig:"Failed to fetch merchant config",invalidConfig:"Invalid merchant config: missing required fields",configFetchFailed:"Configuration fetch failed",unknownError:"Unknown error"}},es:{errors:{targetElementNotFound:"Elemento objetivo no encontrado",failedToInitialize:"Error al inicializar Rinne SDK",failedToProcessApplePay:"Error al procesar datos de Apple Pay",failedToProcessGooglePay:"Error al procesar datos de Google Pay",applePayError:"Error de Apple Pay",googlePayError:"Error de Google Pay",failedToMountApplePay:"Error al montar botón de Apple Pay",failedToMountGooglePay:"Error al montar botón de Google Pay",failedToFetchConfig:"Error al obtener configuración del comerciante",invalidConfig:"Configuración de comerciante inválida: faltan campos requeridos",configFetchFailed:"Error al obtener configuración",unknownError:"Error desconocido"}}};function t(e="pt"){return r[e]}const n={sandbox:"https://api-sandbox.rinne.com.br/core",production:"https://api.rinne.com.br/core"};function a(e){return String(e).padStart(2,"0")}function i(e){const o=String(e);return 2===o.length?`20${o}`:o}function s(e){if(void 0!==e)return 0===e?"0":e}function c(e){return{pt:"pt-BR",en:"en-US",es:"es-ES"}[e]}function l(e,o="pt"){if("string"==typeof e){const r=document.querySelector(e);if(!r){const r=t(o).errors.targetElementNotFound;throw new Error(`${r}: ${e}`)}return r}return e}function d(e,o,r){return{code:e,message:o,details:r}}function u(e,o){return(o,r)=>{let n=!1,u=!1;const p=r.button?.locale??"pt",h=t(p),y=r.button?.size??{width:"100%",height:"40px"},{width:m,height:f}=y,g={locale:c(r.button?.locale??"pt"),type:(v=r.button?.type??"plain",{book:"book",buy:"buy",checkout:"check-out",donate:"donate",order:"order",pay:"pay",plain:"plain",subscribe:"subscribe"}[v]),style:(P=r.button?.color??"black","white"===P?"white-outline":P),borderRadius:s(r.button?.borderRadius),...!!m&&!!f&&{size:{width:m,height:f}},process:async(e,t)=>{if(u||n)return Promise.resolve();try{n=!0;const s=function(e){const{networkToken:o,card:r,cryptogram:t,eci:n,paymentDataType:s,deviceManufacturerIdentifier:c}=e;return{network_token:o.number,expiry_month:a(o.expiry.month),expiry_year:i(o.expiry.year),cryptogram:t,eci:n,token_provider:o.tokenServiceProvider,brand:r.brand,device_id:c,authentication_type:"3DSecure"===s?"3DS":void 0,last_digits:r.lastFour,issuer:"issuer"in r?r.issuer:void 0,segment:"segment"in r?r.segment:void 0,country:"country"in r?r.country:void 0,wallet_type:"APPLE_PAY",display_name:r.displayName}}(e);let c=!1;const l=e=>{c=!0,t.fail(e)};return await(r.onCapture?.({card_data:s,transaction:o},l)),void(c||(u=!0))}catch(s){return t.fail({message:s instanceof Error?s.message:h.errors.failedToProcessApplePay}),Promise.resolve()}finally{u||(n=!1)}}};var P,v;const E=e.ui.applePay(o,g);return E.on("error",(...e)=>{if(n)return;n=!0;const o="string"==typeof e[0]?e[0]:void 0;if("CANCELLED"===o?.toUpperCase())return n=!1,void r.onCancel?.();const t={code:"PROVIDER_ERROR",message:o??h.errors.applePayError};r.onError?.(t)}),E.on("cancel",()=>{n=!1,r.onCancel?.()}),Promise.resolve({async mount(e){try{const o=l(e,p);return await E.mount(o),{unmount:()=>{E.unmount()}}}catch(o){const e=d("PROVIDER_ERROR",o instanceof Error?o.message:h.errors.failedToMountApplePay,o);throw r.onError&&r.onError(e),o}}})}}function p(e,o){return(o,r)=>{let n=!1,c=!1;const u=r.button?.locale??"pt",p=t(u),h=r.button?.size??{width:"100%",height:"40px"},{width:y,height:m}=h,f={locale:r.button?.locale??"pt",type:r.button?.type??"plain",color:r.button?.color??"black",borderRadius:s(r.button?.borderRadius),...!!y&&!!m&&{size:{width:y,height:m}},allowedAuthMethods:["CRYPTOGRAM_3DS"],process:async(e,t)=>{if(c||n)return Promise.resolve();try{n=!0;const s=function(e){if(!function(e){return"token"in e&&"tokenServiceProvider"in e.token}(e))throw new Error("Google Pay response must contain a network token (3DS authentication)");const{token:o,card:r,cryptogram:t,eci:n}=e;return{network_token:o.number,expiry_month:a(o.expiry.month),expiry_year:i(o.expiry.year),cryptogram:t,eci:n??null,token_provider:o.tokenServiceProvider,brand:r.brand,authentication_type:"3DS",last_digits:r.lastFour,issuer:"issuer"in r?r.issuer:void 0,segment:"segment"in r?r.segment:void 0,country:"country"in r?r.country:void 0,wallet_type:"GOOGLE_PAY",display_name:r.displayName}}(e);let l=!1;const d=e=>{l=!0,t.fail(e)};return await(r.onCapture?.({card_data:s,transaction:o},d)),void(l||(c=!0))}catch(s){return t.fail({message:s instanceof Error?s.message:p.errors.failedToProcessGooglePay}),Promise.resolve()}finally{c||(n=!1)}}},g=e.ui.googlePay(o,f);return g.on("error",(...e)=>{if(n)return;n=!0;const o={code:"PROVIDER_ERROR",message:("string"==typeof e[0]?e[0]:void 0)??p.errors.googlePayError};r.onError?.(o)}),g.on("cancel",()=>{n=!1,r.onCancel?.()}),Promise.resolve({mount(e){try{const o=l(e,u);return g.mount(o),Promise.resolve({unmount:()=>{g.unmount()}})}catch(o){const e=d("PROVIDER_ERROR",o instanceof Error?o.message:p.errors.failedToMountGooglePay,o);throw r.onError&&r.onError(e),o}}})}}exports.Rinne=class{constructor(e){this.options=e,this.transaction=this.createTransactionNamespace(),this.elements=this.createElementsNamespace()}config=null;evervault=null;initPromise=null;transaction;elements;async initialize(){return this.initPromise||(this.initPromise=(async()=>{try{const e=await async function(e){const o=t("pt"),r=`${e.baseUrl??n[e.environment??"production"]}/merchants/${e.merchantId}/public-settings`;try{const e=await fetch(r);if(!e.ok){const r=String(e.status);throw new Error(`${o.errors.failedToFetchConfig}: ${r} ${e.statusText}`)}const t=await e.json();if(!t.merchant_id||!t.team_id||!t.app_id)throw new Error(o.errors.invalidConfig);return{merchantId:t.merchant_id,teamId:t.team_id,appId:t.app_id,availableMethods:t.available_methods,name:t.name}}catch(a){if(a instanceof Error)throw new Error(`${o.errors.configFetchFailed}: ${a.message}`);throw new Error(`${o.errors.configFetchFailed}: ${o.errors.unknownError}`)}}({merchantId:this.options.merchantId,environment:this.options.environment??"production",baseUrl:this.options.rinneUrl});this.config=e,this.evervault=await async function(e,r,t){return new(await o())(e,r,t)}(this.config.teamId,this.config.appId)}catch(e){throw this.initPromise=null,e}})()),this.initPromise}createElementsNamespace(){return{applePay:async(e,o)=>{if(await this.initialize(),!this.evervault||!this.config){const e=t(o.button?.locale??"pt");throw new Error(e.errors.failedToInitialize)}return u(this.evervault,this.config)(e,o)},googlePay:async(e,o)=>{if(await this.initialize(),!this.evervault||!this.config){const e=t(o.button?.locale??"pt");throw new Error(e.errors.failedToInitialize)}return p(this.evervault,this.config)(e,o)}}}createTransactionNamespace(){return{create:async e=>{if(await this.initialize(),!this.evervault||!this.config?.merchantId){const e=t("pt");throw new Error(e.errors.failedToInitialize)}return this.evervault.transactions.create({type:"payment",...e,merchantId:this.config.merchantId})}}}};
@@ -0,0 +1,131 @@
1
+ import { EvTransaction } from './evervault-types';
2
+
3
+ export declare interface ApplePayElement {
4
+ mount(target: string | HTMLElement): Promise<MountedElement>;
5
+ }
6
+
7
+ export declare type ApplePayElementNamespace = (transaction: EvTransaction, options: ApplePayMountOptions) => Promise<ApplePayElement>;
8
+
9
+ export declare interface ApplePayMountOptions extends WalletHandlers {
10
+ button?: WalletButtonOptions;
11
+ }
12
+
13
+ export declare interface CardData {
14
+ number?: string;
15
+ network_token?: string;
16
+ expiry_month: string;
17
+ expiry_year: string;
18
+ cvv?: string;
19
+ cryptogram: string;
20
+ eci?: string | null;
21
+ token_provider?: string;
22
+ brand?: string;
23
+ device_id?: string;
24
+ authentication_type?: string;
25
+ last_digits?: string;
26
+ issuer?: string;
27
+ segment?: string;
28
+ country?: string;
29
+ wallet_type?: WalletType;
30
+ display_name?: string;
31
+ }
32
+
33
+ export declare interface ElementsNamespace {
34
+ applePay: ApplePayElementNamespace;
35
+ googlePay: GooglePayElementNamespace;
36
+ }
37
+
38
+ export declare type Environment = 'sandbox' | 'production';
39
+
40
+ export { EvTransaction }
41
+
42
+ export declare interface GooglePayElement {
43
+ mount(target: string | HTMLElement): Promise<MountedElement>;
44
+ }
45
+
46
+ export declare type GooglePayElementNamespace = (transaction: EvTransaction, options: GooglePayMountOptions) => Promise<GooglePayElement>;
47
+
48
+ export declare interface GooglePayMountOptions extends WalletHandlers {
49
+ button?: WalletButtonOptions;
50
+ }
51
+
52
+ export declare interface MountedElement {
53
+ unmount(): void;
54
+ }
55
+
56
+ export declare interface Options {
57
+ merchantId: string;
58
+ environment?: Environment;
59
+ rinneUrl?: string;
60
+ }
61
+
62
+ export declare class Rinne {
63
+ private options;
64
+ private config;
65
+ private evervault;
66
+ private initPromise;
67
+ readonly transaction: TransactionNamespace;
68
+ readonly elements: ElementsNamespace;
69
+ constructor(options: Options);
70
+ private initialize;
71
+ private createElementsNamespace;
72
+ private createTransactionNamespace;
73
+ }
74
+
75
+ export declare interface TransactionCreateParams {
76
+ amount: number;
77
+ currency: string;
78
+ country?: string;
79
+ lineItems?: WalletLineItem[];
80
+ }
81
+
82
+ export declare interface TransactionNamespace {
83
+ create(params: TransactionCreateParams): Promise<EvTransaction>;
84
+ }
85
+
86
+ declare type WalletButtonColor = 'black' | 'white';
87
+
88
+ export declare interface WalletButtonOptions {
89
+ color?: WalletButtonColor;
90
+ locale?: WalletLocale;
91
+ type?: WalletButtonType;
92
+ borderRadius?: number;
93
+ size?: {
94
+ width: number | string;
95
+ height: number | string;
96
+ };
97
+ }
98
+
99
+ export declare type WalletButtonType = 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe';
100
+
101
+ export declare interface WalletError {
102
+ code: WalletErrorCode;
103
+ message: string;
104
+ details?: unknown;
105
+ }
106
+
107
+ export declare type WalletErrorCode = 'PROVIDER_ERROR' | 'VALIDATION_ERROR' | 'CONFIGURATION_ERROR' | 'UNKNOWN';
108
+
109
+ export declare interface WalletHandlers {
110
+ onCapture?: (payload: WalletSuccessPayload, fail: (error: {
111
+ message: string;
112
+ }) => void) => Promise<void> | void;
113
+ onError?: (error: WalletError) => void;
114
+ onCancel?: () => void;
115
+ }
116
+
117
+ export declare interface WalletLineItem {
118
+ amount: number;
119
+ label: string;
120
+ }
121
+
122
+ export declare type WalletLocale = 'pt' | 'en' | 'es';
123
+
124
+ export declare interface WalletSuccessPayload {
125
+ card_data: CardData;
126
+ transaction: EvTransaction;
127
+ }
128
+
129
+ export declare type WalletType = 'APPLE_PAY' | 'GOOGLE_PAY';
130
+
131
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ let e=null;async function o(){if(window.Evervault)return window.Evervault;try{return await async function(){return e||(e=new Promise((o,r)=>{const t=document.createElement("script");if(t.src="https://js.evervault.com/v2",t.onload=()=>o(),t.onerror=()=>{e=null,r()},!document.head)throw new Error("Expected document.head not to be null. Evervault.js requires a <head> element.");document.head.appendChild(t)}),e)}(),window.Evervault}catch{throw new Error("Failed to load Evervault.js")}}Promise.resolve().then(()=>{o()});const r={pt:{errors:{targetElementNotFound:"Elemento alvo não encontrado",failedToInitialize:"Falha ao inicializar Rinne SDK",failedToProcessApplePay:"Falha ao processar dados do Apple Pay",failedToProcessGooglePay:"Falha ao processar dados do Google Pay",applePayError:"Erro do Apple Pay",googlePayError:"Erro do Google Pay",failedToMountApplePay:"Falha ao montar botão do Apple Pay",failedToMountGooglePay:"Falha ao montar botão do Google Pay",failedToFetchConfig:"Falha ao buscar configuração do comerciante",invalidConfig:"Configuração de comerciante inválida: campos obrigatórios ausentes",configFetchFailed:"Falha ao buscar configuração",unknownError:"Erro desconhecido"}},en:{errors:{targetElementNotFound:"Target element not found",failedToInitialize:"Failed to initialize Rinne SDK",failedToProcessApplePay:"Failed to process Apple Pay data",failedToProcessGooglePay:"Failed to process Google Pay data",applePayError:"Apple Pay error occurred",googlePayError:"Google Pay error occurred",failedToMountApplePay:"Failed to mount Apple Pay button",failedToMountGooglePay:"Failed to mount Google Pay button",failedToFetchConfig:"Failed to fetch merchant config",invalidConfig:"Invalid merchant config: missing required fields",configFetchFailed:"Configuration fetch failed",unknownError:"Unknown error"}},es:{errors:{targetElementNotFound:"Elemento objetivo no encontrado",failedToInitialize:"Error al inicializar Rinne SDK",failedToProcessApplePay:"Error al procesar datos de Apple Pay",failedToProcessGooglePay:"Error al procesar datos de Google Pay",applePayError:"Error de Apple Pay",googlePayError:"Error de Google Pay",failedToMountApplePay:"Error al montar botón de Apple Pay",failedToMountGooglePay:"Error al montar botón de Google Pay",failedToFetchConfig:"Error al obtener configuración del comerciante",invalidConfig:"Configuración de comerciante inválida: faltan campos requeridos",configFetchFailed:"Error al obtener configuración",unknownError:"Error desconocido"}}};function t(e="pt"){return r[e]}const n={sandbox:"https://api-sandbox.rinne.com.br/core",production:"https://api.rinne.com.br/core"};function a(e){return String(e).padStart(2,"0")}function i(e){const o=String(e);return 2===o.length?`20${o}`:o}function s(e){if(void 0!==e)return 0===e?"0":e}function c(e){return{pt:"pt-BR",en:"en-US",es:"es-ES"}[e]}function l(e,o="pt"){if("string"==typeof e){const r=document.querySelector(e);if(!r){const r=t(o).errors.targetElementNotFound;throw new Error(`${r}: ${e}`)}return r}return e}function d(e,o,r){return{code:e,message:o,details:r}}function u(e,o){return(o,r)=>{let n=!1,u=!1;const p=r.button?.locale??"pt",h=t(p),m=r.button?.size??{width:"100%",height:"40px"},{width:y,height:f}=m,g={locale:c(r.button?.locale??"pt"),type:(v=r.button?.type??"plain",{book:"book",buy:"buy",checkout:"check-out",donate:"donate",order:"order",pay:"pay",plain:"plain",subscribe:"subscribe"}[v]),style:(P=r.button?.color??"black","white"===P?"white-outline":P),borderRadius:s(r.button?.borderRadius),...!!y&&!!f&&{size:{width:y,height:f}},process:async(e,t)=>{if(u||n)return Promise.resolve();try{n=!0;const s=function(e){const{networkToken:o,card:r,cryptogram:t,eci:n,paymentDataType:s,deviceManufacturerIdentifier:c}=e;return{network_token:o.number,expiry_month:a(o.expiry.month),expiry_year:i(o.expiry.year),cryptogram:t,eci:n,token_provider:o.tokenServiceProvider,brand:r.brand,device_id:c,authentication_type:"3DSecure"===s?"3DS":void 0,last_digits:r.lastFour,issuer:"issuer"in r?r.issuer:void 0,segment:"segment"in r?r.segment:void 0,country:"country"in r?r.country:void 0,wallet_type:"APPLE_PAY",display_name:r.displayName}}(e);let c=!1;const l=e=>{c=!0,t.fail(e)};return await(r.onCapture?.({card_data:s,transaction:o},l)),void(c||(u=!0))}catch(s){return t.fail({message:s instanceof Error?s.message:h.errors.failedToProcessApplePay}),Promise.resolve()}finally{u||(n=!1)}}};var P,v;const E=e.ui.applePay(o,g);return E.on("error",(...e)=>{if(n)return;n=!0;const o="string"==typeof e[0]?e[0]:void 0;if("CANCELLED"===o?.toUpperCase())return n=!1,void r.onCancel?.();const t={code:"PROVIDER_ERROR",message:o??h.errors.applePayError};r.onError?.(t)}),E.on("cancel",()=>{n=!1,r.onCancel?.()}),Promise.resolve({async mount(e){try{const o=l(e,p);return await E.mount(o),{unmount:()=>{E.unmount()}}}catch(o){const e=d("PROVIDER_ERROR",o instanceof Error?o.message:h.errors.failedToMountApplePay,o);throw r.onError&&r.onError(e),o}}})}}function p(e,o){return(o,r)=>{let n=!1,c=!1;const u=r.button?.locale??"pt",p=t(u),h=r.button?.size??{width:"100%",height:"40px"},{width:m,height:y}=h,f={locale:r.button?.locale??"pt",type:r.button?.type??"plain",color:r.button?.color??"black",borderRadius:s(r.button?.borderRadius),...!!m&&!!y&&{size:{width:m,height:y}},allowedAuthMethods:["CRYPTOGRAM_3DS"],process:async(e,t)=>{if(c||n)return Promise.resolve();try{n=!0;const s=function(e){if(!function(e){return"token"in e&&"tokenServiceProvider"in e.token}(e))throw new Error("Google Pay response must contain a network token (3DS authentication)");const{token:o,card:r,cryptogram:t,eci:n}=e;return{network_token:o.number,expiry_month:a(o.expiry.month),expiry_year:i(o.expiry.year),cryptogram:t,eci:n??null,token_provider:o.tokenServiceProvider,brand:r.brand,authentication_type:"3DS",last_digits:r.lastFour,issuer:"issuer"in r?r.issuer:void 0,segment:"segment"in r?r.segment:void 0,country:"country"in r?r.country:void 0,wallet_type:"GOOGLE_PAY",display_name:r.displayName}}(e);let l=!1;const d=e=>{l=!0,t.fail(e)};return await(r.onCapture?.({card_data:s,transaction:o},d)),void(l||(c=!0))}catch(s){return t.fail({message:s instanceof Error?s.message:p.errors.failedToProcessGooglePay}),Promise.resolve()}finally{c||(n=!1)}}},g=e.ui.googlePay(o,f);return g.on("error",(...e)=>{if(n)return;n=!0;const o={code:"PROVIDER_ERROR",message:("string"==typeof e[0]?e[0]:void 0)??p.errors.googlePayError};r.onError?.(o)}),g.on("cancel",()=>{n=!1,r.onCancel?.()}),Promise.resolve({mount(e){try{const o=l(e,u);return g.mount(o),Promise.resolve({unmount:()=>{g.unmount()}})}catch(o){const e=d("PROVIDER_ERROR",o instanceof Error?o.message:p.errors.failedToMountGooglePay,o);throw r.onError&&r.onError(e),o}}})}}class h{constructor(e){this.options=e,this.transaction=this.createTransactionNamespace(),this.elements=this.createElementsNamespace()}config=null;evervault=null;initPromise=null;transaction;elements;async initialize(){return this.initPromise||(this.initPromise=(async()=>{try{const e=await async function(e){const o=t("pt"),r=`${e.baseUrl??n[e.environment??"production"]}/merchants/${e.merchantId}/public-settings`;try{const e=await fetch(r);if(!e.ok){const r=String(e.status);throw new Error(`${o.errors.failedToFetchConfig}: ${r} ${e.statusText}`)}const t=await e.json();if(!t.merchant_id||!t.team_id||!t.app_id)throw new Error(o.errors.invalidConfig);return{merchantId:t.merchant_id,teamId:t.team_id,appId:t.app_id,availableMethods:t.available_methods,name:t.name}}catch(a){if(a instanceof Error)throw new Error(`${o.errors.configFetchFailed}: ${a.message}`);throw new Error(`${o.errors.configFetchFailed}: ${o.errors.unknownError}`)}}({merchantId:this.options.merchantId,environment:this.options.environment??"production",baseUrl:this.options.rinneUrl});this.config=e,this.evervault=await async function(e,r,t){return new(await o())(e,r,t)}(this.config.teamId,this.config.appId)}catch(e){throw this.initPromise=null,e}})()),this.initPromise}createElementsNamespace(){return{applePay:async(e,o)=>{if(await this.initialize(),!this.evervault||!this.config){const e=t(o.button?.locale??"pt");throw new Error(e.errors.failedToInitialize)}return u(this.evervault,this.config)(e,o)},googlePay:async(e,o)=>{if(await this.initialize(),!this.evervault||!this.config){const e=t(o.button?.locale??"pt");throw new Error(e.errors.failedToInitialize)}return p(this.evervault,this.config)(e,o)}}}createTransactionNamespace(){return{create:async e=>{if(await this.initialize(),!this.evervault||!this.config?.merchantId){const e=t("pt");throw new Error(e.errors.failedToInitialize)}return this.evervault.transactions.create({type:"payment",...e,merchantId:this.config.merchantId})}}}}export{h as Rinne};
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@rinnebr/js",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Modern payment elements library for Apple Pay, Google Pay, and other payment methods",
5
+ "type": "module",
6
+ "packageManager": "yarn@4.9.1",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "yarn check && vite build",
28
+ "check": "yarn type-check && yarn lint && yarn format:check && yarn test",
29
+ "type-check": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "test:ui": "vitest --ui",
33
+ "lint": "eslint .",
34
+ "lint:fix": "eslint . --fix",
35
+ "format": "prettier --write .",
36
+ "format:check": "prettier --check .",
37
+ "prepare": "husky",
38
+ "prepublishOnly": "yarn build"
39
+ },
40
+ "keywords": [
41
+ "payment",
42
+ "apple-pay",
43
+ "google-pay",
44
+ "payment-elements",
45
+ "payment-buttons"
46
+ ],
47
+ "author": "Rinne",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/rinnebr/rinne-js.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/rinnebr/rinne-js/issues"
55
+ },
56
+ "homepage": "https://github.com/rinnebr/rinne-js#readme",
57
+ "devDependencies": {
58
+ "@eslint/js": "^9.39.1",
59
+ "@types/jsdom": "^27",
60
+ "@types/node": "^24.10.1",
61
+ "@vitest/ui": "^4.0.8",
62
+ "eslint": "^9",
63
+ "eslint-config-prettier": "^10.1.8",
64
+ "husky": "^9.1.7",
65
+ "jsdom": "^27.2.0",
66
+ "lint-staged": "^16.2.6",
67
+ "prettier": "^3.6.2",
68
+ "terser": "^5.44.1",
69
+ "typescript": "^5.9.3",
70
+ "typescript-eslint": "^8.46.4",
71
+ "vite": "^7.2.2",
72
+ "vite-plugin-dts": "^4.5.4",
73
+ "vitest": "^4.0.8"
74
+ },
75
+ "dependencies": {
76
+ "@evervault/js": "^2.6.0"
77
+ }
78
+ }