Skip to content

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
DateChange
2026-03-19Added connected account support and fixed scope handling.
2026-03-19Initial publication.

ShopHero CommerceCore Platform