Use this guide to implement a production wallet flow where the frontend handles wallet UX and your backend performs Core API calls.
The core contract is simple: capture encrypted payload.card_data in onCapture and forward it unchanged to your server.
Flow Overview
- Initialize SDK in the frontend
- Create a transaction
- Mount wallet buttons
- On capture, send encrypted data to backend
- Backend creates transaction in Rinne API
Forward payload.card_data as encrypted secure data to your backend. Avoid logging sensitive payload fields in plaintext logs.
Frontend Implementation
import { Rinne } from '@rinnebr/js'
const rinne = new Rinne({
merchantId: 'your-merchant-id',
environment: 'production'
})
const transaction = await rinne.transaction.create({
amount: 3990,
currency: 'BRL',
country: 'BR',
lineItems: [{ label: 'Order Total', amount: 3990 }]
})
const handlers = {
onCapture: async (payload, fail) => {
try {
const response = await fetch('/api/checkout/wallet', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cardData: payload.card_data,
paymentMethod: payload.payment_method,
amount: payload.transaction.details.amount,
currency: payload.transaction.details.currency,
requestId: crypto.randomUUID()
})
})
if (!response.ok) {
const body = await response.json()
throw new Error(body.message ?? 'Wallet payment failed')
}
} catch (error) {
fail({ message: error instanceof Error ? error.message : 'Wallet payment failed' })
}
},
onError: (error) => console.error('Wallet error:', error),
onCancel: () => console.log('Wallet cancelled')
}
const applePay = await rinne.elements.applePay(transaction, {
button: { type: 'pay', color: 'black', locale: 'pt' },
...handlers
})
await applePay.mount('#apple-pay')
const googlePay = await rinne.elements.googlePay(transaction, {
button: { type: 'pay', color: 'black', locale: 'pt' },
colorScheme: 'dark',
...handlers
})
await googlePay.mount('#google-pay')
Backend Example (Node.js)
app.post('/api/checkout/wallet', async (req, res) => {
const { cardData, paymentMethod, amount, currency, requestId } = req.body
try {
const rinneResponse = await fetch(
`https://api.rinne.com.br/core/v1/merchants/${process.env.RINNE_MERCHANT_ID}/transactions`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.RINNE_API_KEY
},
body: JSON.stringify({
amount,
currency,
provider: 'RINNE',
payment_method: paymentMethod,
capture_method: 'ECOMMERCE',
request_id: requestId,
card_data: cardData,
installments: 1
})
}
)
const body = await rinneResponse.json()
if (!rinneResponse.ok) {
return res.status(rinneResponse.status).json({ message: body.message ?? 'Charge failed' })
}
return res.status(200).json(body)
} catch (error) {
return res.status(500).json({ message: 'Internal wallet processing error' })
}
})
Never call Rinne transaction APIs directly from the browser. Keep your API key server-side.
Verification Checklist
Wallet checkout is correctly implemented when:
onCapture sends encrypted payloads to your backend
- Backend creates transaction with your API key
fail() is called whenever backend processing fails
- If transaction status is
AWAITING_3DS, your checkout continues with the 3DS flow