Appearance
JavaScript Example
A complete checkout integration using vanilla JavaScript and Stripe.js.
Full Checkout Flow
javascript
import { loadStripe } from '@stripe/stripe-js';
let stripe;
let elements;
let stripeAccountId;
/**
* Initialize Stripe with config from TransactCore.
* Call this once when the checkout page loads.
*/
async function initializeStripe(apiToken) {
// 1. Fetch Stripe config from your backend
const configResponse = await fetch('/api/stripe-config', {
headers: { 'Authorization': `Bearer ${apiToken}` },
});
const config = await configResponse.json();
// 2. Initialize Stripe.js
stripe = await loadStripe(config.publishable_key);
stripeAccountId = config.stripe_account_id || null;
}
/**
* Mount the Stripe Payment Element.
* Call this after initializeStripe() and once you know the order total.
*/
function mountPaymentElement(amountInCents) {
const elementsOptions = {
mode: 'payment',
amount: amountInCents,
currency: 'usd',
appearance: { theme: 'stripe' },
};
// For Stripe Connect: route payments to a specific connected account
if (stripeAccountId) {
elementsOptions.on_behalf_of = stripeAccountId;
}
elements = stripe.elements(elementsOptions);
const paymentElement = elements.create('payment', { layout: 'tabs' });
paymentElement.mount('#payment-element');
}
/**
* Process the payment.
* Call this when the customer clicks "Pay".
*/
async function processPayment(apiToken, orderData) {
// 1. Validate the Payment Element input
const { error: submitError } = await elements.submit();
if (submitError) {
showError(submitError.message);
return;
}
// 2. Create PaymentIntent via your backend
const intentResponse = await fetch('/api/payment-intent', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: orderData.totalInCents,
organization_id: orderData.organizationId,
}),
});
const { client_secret, payment_intent_id } = await intentResponse.json();
// 3. Confirm payment with Stripe.js
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
clientSecret: client_secret,
confirmParams: {
payment_method_data: {
billing_details: {
name: orderData.customerName,
email: orderData.customerEmail,
},
},
return_url: window.location.href,
},
redirect: 'if_required',
});
if (error) {
showError(error.message);
return;
}
// 4. Payment succeeded — submit the order
if (paymentIntent.status === 'succeeded') {
try {
await submitOrder(payment_intent_id, orderData);
showSuccess('Order placed successfully!');
} catch (orderError) {
// Order creation failed — cancel the payment intent
await fetch(`/api/payment-intent/${payment_intent_id}/cancel`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiToken}` },
});
showError('Order could not be placed. Your payment has been cancelled.');
}
}
}
function showError(message) {
document.getElementById('payment-message').textContent = message;
}
function showSuccess(message) {
document.getElementById('payment-message').textContent = message;
}HTML Structure
html
<form id="checkout-form">
<div id="payment-element">
<!-- Stripe Payment Element mounts here -->
</div>
<button type="submit" id="pay-button">Pay</button>
<div id="payment-message"></div>
</form>
<script type="module">
// Initialize on page load
const token = await getAuthToken();
await initializeStripe(token);
mountPaymentElement(3420); // $34.20
document.getElementById('checkout-form').addEventListener('submit', async (e) => {
e.preventDefault();
const button = document.getElementById('pay-button');
button.disabled = true;
await processPayment(token, {
totalInCents: 3420,
organizationId: 'org_00000k1L2m3N4o5',
customerName: 'John Doe',
customerEmail: 'john@example.com',
});
button.disabled = false;
});
</script>Backend Proxy Endpoints
Your backend should proxy requests to TransactCore. Here's a minimal Node.js/Express example:
javascript
import express from 'express';
const app = express();
const TRANSACTCORE_URL = 'https://api.transactcore.retailsuccessplatform.com/api/v1/ecommerce';
// Fetch a service token from Identity (cache this!)
async function getServiceToken() {
const response = await fetch('https://identity.retailsuccessplatform.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
scope: 'transactcore:payment-intents.read transactcore:payment-intents.create',
}),
});
const { access_token } = await response.json();
return access_token;
}
// GET /api/stripe-config
app.get('/api/stripe-config', async (req, res) => {
const token = await getServiceToken();
const response = await fetch(`${TRANSACTCORE_URL}/config/stripe`, {
headers: { 'Authorization': `Bearer ${token}` },
});
res.json(await response.json());
});
// POST /api/payment-intent
app.post('/api/payment-intent', express.json(), async (req, res) => {
const token = await getServiceToken();
const response = await fetch(`${TRANSACTCORE_URL}/payment-intents`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
});
res.status(response.status).json(await response.json());
});
// POST /api/payment-intent/:id/cancel
app.post('/api/payment-intent/:id/cancel', async (req, res) => {
const token = await getServiceToken();
const response = await fetch(`${TRANSACTCORE_URL}/payment-intents/${req.params.id}/cancel`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
});
res.status(response.status).json(await response.json());
});
app.listen(3000);Error Handling
javascript
async function processPaymentWithErrorHandling(apiToken, orderData) {
try {
// Validate payment element
const { error: submitError } = await elements.submit();
if (submitError) {
throw new Error(submitError.message);
}
// Create PaymentIntent
const intentResponse = await fetch('/api/payment-intent', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: orderData.totalInCents,
organization_id: orderData.organizationId,
}),
});
if (!intentResponse.ok) {
const errorData = await intentResponse.json();
throw new Error(errorData.error || 'Failed to create payment intent');
}
const { client_secret } = await intentResponse.json();
// Confirm payment
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
clientSecret: client_secret,
confirmParams: {
return_url: window.location.href,
},
redirect: 'if_required',
});
if (error) {
// Card errors are safe to show to customers
if (error.type === 'card_error' || error.type === 'validation_error') {
throw new Error(error.message);
}
throw new Error('An unexpected error occurred.');
}
return paymentIntent;
} catch (err) {
showError(err.message);
return null;
}
}Changelog
| Date | Change |
|---|---|
| 2026-03-19 | Added connected account support and fixed scope handling. |
| 2026-03-19 | Initial publication. |