Skip to main content
The simplest checkout flow possible: Go from cart to completed order in a single API call. Perfect for merchants who want to maximize conversion with minimal friction.

Why 1-Step Checkout?

Traditional checkout flows require multiple steps:
  1. Create cart → 2. Add items → 3. Set shipping → 4. Calculate tax → 5. Process payment → 6. Complete order
With Firmly’s place-order API, you can do all of this in one single API call.

Benefits

Higher Conversion

Reduce checkout abandonment with fewer steps

Faster Integration

Implement in hours, not weeks

Less Complexity

One endpoint to maintain instead of six

How It Works

The place-order API combines cart creation, item addition, shipping calculation, and payment processing into a single atomic operation:
Real-Time Updates: The place-order API supports Server-Sent Events (SSE) for real-time order processing updates. Perfect for showing progress indicators during checkout.

Quick Implementation

1

Collect Customer Information

Gather items, shipping address, and credit card details in your checkout form
2

Encrypt Credit Card

Use Firmly’s public key to JWE-encrypt card details for secure transmission
3

Call place-order API

Submit everything in one API call and handle the response

Credit Card Encryption

Security First: Never send raw credit card data to any API. Always encrypt card details using JWE (JSON Web Encryption) with Firmly’s public key.
The credit card data must be encrypted as a JWE token containing:
{
  "number": "4111111111111111",      // Card number
  "name": "John Smith",              // Cardholder name
  "verification_value": "123",       // CVV/CVC
  "month": "08",                     // Expiry month (2 digits)
  "year": "2025"                     // Expiry year (4 digits)
}

Complete Example

Here’s a full implementation of 1-step checkout:
// First, install the jose library: npm install jose
import * as jose from 'jose';

async function oneStepCheckout(checkoutData, domain, authToken) {
  // Step 1: Get public key for encryption
  const publicKeyResponse = await fetch(
    'https://cc.firmly.work/api/v1/payment/key',
    {
      headers: {
        'x-firmly-authorization': authToken
      }
    }
  );
  const publicKeyJWK = await publicKeyResponse.json();

  // Step 2: Encrypt credit card data using JWE
  const cardData = {
    number: checkoutData.cardNumber,
    name: checkoutData.cardholderName,
    verification_value: checkoutData.cvv,
    month: checkoutData.expMonth,  // e.g., "08"
    year: checkoutData.expYear     // e.g., "2025"
  };

  // Import the public key
  const publicKey = await jose.importJWK(publicKeyJWK, 'RSA-OAEP-256');
  
  // Create JWE encrypted card
  const encryptedCard = await new jose.CompactEncrypt(
    new TextEncoder().encode(JSON.stringify(cardData))
  )
    .setProtectedHeader({ 
      alg: 'RSA-OAEP-256', 
      enc: 'A256GCM',
      kid: publicKeyJWK.kid 
    })
    .encrypt(publicKey);

  // Step 3: Place order in one call
  const orderResponse = await fetch(
    `https://cc.firmly.work/api/v1/payment/domains/${domain}/place-order`,
    {
      method: 'POST',
      headers: {
        'x-firmly-authorization': authToken,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        items: [
          {
            variant_id: 'WS12-XS-Orange',
            quantity: 2
          }
        ],
        shipping_info: {
          first_name: 'John',
          last_name: 'Doe',
          address1: '123 Main St',
          city: 'New York',
          state_or_province: 'NY',
          postal_code: '10001',
          country: 'US',
          email: '[email protected]',
          phone: '555-1234'
        },
        billing_info: {
          // Same structure as shipping_info
          // Or omit if billing same as shipping
          first_name: 'John',
          last_name: 'Doe',
          address1: '123 Main St',
          city: 'New York',
          state_or_province: 'NY',
          postal_code: '10001',
          country: 'US',
          email: '[email protected]',
          phone: '555-1234'
        },
        encrypted_card: encryptedCard
      })
    }
  );

  // Handle response
  const result = await orderResponse.json();
  
  if (result.cart_id) {
    // Success! Order completed
    return {
      success: true,
      cartId: result.cart_id,
      orderNumber: result.platform_order_number
    };
  } else {
    // Handle error
    return {
      success: false,
      error: result.error
    };
  }
}

// Example usage:
const checkoutData = {
  cardNumber: '4242424242424242',
  cardholderName: 'John Doe',
  cvv: '123',
  expMonth: '12',
  expYear: '2025'
};

const domain = 'staging.luma.gift';
const authToken = 'YOUR_AUTH_TOKEN'; // Get this from browser session

// Call the function
oneStepCheckout(checkoutData, domain, authToken)
  .then(result => {
    if (result.success) {
      console.log('Order placed successfully!', result.orderNumber);
    } else {
      console.error('Order failed:', result.error);
    }
  });

Important Security Notes

Critical Security Requirements:
  1. Never store card data - Card details should only exist in memory during encryption
  2. Always use HTTPS - All API calls must be over secure connections
  3. PCI Compliance - Ensure your implementation follows PCI DSS guidelines
  4. Server-side encryption - Never expose encryption logic to client-side JavaScript
  5. Validate SSL certificates - Ensure you’re connecting to genuine Firmly endpoints

Real-Time Features with SSE

Server-Sent Events Support: Both place-order endpoints (v1 and v2) support SSE for real-time order status updates. This enables you to show live progress during checkout processing.

Using SSE for Live Updates

// Option 1: Use SSE by setting Accept header
const domain = 'staging.luma.gift';
const authToken = 'YOUR_AUTH_TOKEN'; // Get this from browser session
const orderData = {
  items: [{ variant_id: 'WS12-XS-Orange', quantity: 1 }],
  shipping_info: { /* ... */ },
  billing_info: { /* ... */ },
  encrypted_card: encryptedCard
};

const response = await fetch(
  `https://cc.firmly.work/api/v1/payment/domains/${domain}/place-order`,
  {
    method: 'POST',
    headers: {
      'x-firmly-authorization': authToken,
      'Content-Type': 'application/json',
      'Accept': 'text/event-stream'  // Enable SSE
    },
    body: JSON.stringify(orderData)
  }
);

// Read the event stream
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  
  const chunk = decoder.decode(value);
  // Parse SSE events from chunk
  console.log('Event:', chunk);
}

SSE Event Types

  • order_processing: Order is being validated and processed
  • payment_processing: Payment is being authorized
  • order_completed: Order successfully placed with order details
  • error: Processing failed with error details

When to Use 1-Step Checkout

  • Single vendor stores
  • Standard shipping only
  • High-volume, low-complexity products
  • Mobile-first experiences

Error Handling

The place-order API provides detailed error responses:
{
  "code": 400,
  "error": "InvalidAddress",
  "description": "Shipping address could not be validated"
}
Common error scenarios:
  • InvalidAddress: Address validation failed
  • InsufficientStock: Product not available
  • PaymentFailed: Card declined or invalid
  • InvalidVariant: Product SKU not found
  • InvalidCard: Card encryption or validation failed

Next Steps

Support

Need help implementing 1-step checkout?