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:- Create cart → 2. Add items → 3. Set shipping → 4. Calculate tax → 5. Process payment → 6. Complete order
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
Theplace-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.
{
"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: 'john@staging.luma.gift',
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: 'john@staging.luma.gift',
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);
}
});
import requests
import json
from joserfc import jwt
from joserfc.jwe import encrypt_compact
from joserfc.jwk import RSAKey
def one_step_checkout(checkout_data, domain, auth_token):
# Step 1: Get public key in JWK format (default)
public_key_response = requests.get(
"https://cc.firmly.work/api/v1/payment/key",
headers={
'x-firmly-authorization': auth_token
}
)
public_key_jwk = public_key_response.json()
# Step 2: Prepare card data
card_data = {
'number': checkout_data['card_number'],
'name': checkout_data['cardholder_name'],
'verification_value': checkout_data['cvv'],
'month': checkout_data['exp_month'], # e.g., "08"
'year': checkout_data['exp_year'] # e.g., "2025"
}
# Step 3: Encrypt using JWE with JWK
# Import the JWK
public_key = RSAKey.import_key(public_key_jwk)
# Create JWE encrypted card
protected_header = {
"alg": "RSA-OAEP-256",
"enc": "A256GCM",
"kid": public_key_jwk['kid'] # Get kid from JWK response
}
encrypted_card = encrypt_compact(
json.dumps(card_data).encode('utf-8'),
public_key,
algorithm="RSA-OAEP-256",
encryption="A256GCM",
protected=protected_header
)
# Step 4: Place order
order_data = {
'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': 'john@staging.luma.gift',
'phone': '555-1234'
},
'billing_info': {
# Same structure as 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': 'john@staging.luma.gift',
'phone': '555-1234'
},
'encrypted_card': encrypted_card
}
response = requests.post(
f"https://cc.firmly.work/api/v1/payment/domains/{domain}/place-order",
headers={
'x-firmly-authorization': auth_token,
'Content-Type': 'application/json'
},
json=order_data
)
result = response.json()
if 'cart_id' in result:
return {
'success': True,
'cart_id': result['cart_id'],
'order_number': result['platform_order_number']
}
else:
return {
'success': False,
'error': result.get('error')
}
# Example usage:
checkout_data = {
'card_number': '4242424242424242',
'cardholder_name': 'John Doe',
'cvv': '123',
'exp_month': '12',
'exp_year': '2025'
}
domain = 'staging.luma.gift'
auth_token = 'YOUR_AUTH_TOKEN' # Get this from browser session
# Call the function
result = one_step_checkout(checkout_data, domain, auth_token)
if result['success']:
print(f"Order placed successfully! Order Number: {result['order_number']}")
else:
print(f"Order failed: {result['error']}")
// Dependencies:
// - com.nimbusds:nimbus-jose-jwt:9.31
// - com.fasterxml.jackson.core:jackson-databind:2.15.2
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.RSAKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
public class OneStepCheckout {
private static final String API_BASE = "https://cc.firmly.work";
private static final ObjectMapper mapper = new ObjectMapper();
public static CheckoutResult performCheckout(
CheckoutData checkoutData,
String domain,
String authToken
) throws Exception {
// Step 1: Get public key in JWK format
HttpClient client = HttpClient.newHttpClient();
HttpRequest keyRequest = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/api/v1/payment/key"))
.header("x-firmly-authorization", authToken)
.GET()
.build();
HttpResponse<String> keyResponse = client.send(
keyRequest,
HttpResponse.BodyHandlers.ofString()
);
Map<String, Object> jwkData = mapper.readValue(
keyResponse.body(),
Map.class
);
// Step 2: Prepare card data
Map<String, String> cardData = new HashMap<>();
cardData.put("number", checkoutData.getCardNumber());
cardData.put("name", checkoutData.getCardholderName());
cardData.put("verification_value", checkoutData.getCvv());
cardData.put("month", checkoutData.getExpMonth()); // e.g., "08"
cardData.put("year", checkoutData.getExpYear()); // e.g., "2025"
// Step 3: Encrypt using JWE
RSAKey rsaKey = RSAKey.parse(mapper.writeValueAsString(jwkData));
// Create JWE object
JWEHeader header = new JWEHeader.Builder(
JWEAlgorithm.RSA_OAEP_256,
EncryptionMethod.A256GCM
)
.keyID(jwkData.get("kid").toString())
.build();
Payload payload = new Payload(mapper.writeValueAsString(cardData));
JWEObject jweObject = new JWEObject(header, payload);
// Encrypt
jweObject.encrypt(new RSAEncrypter(rsaKey));
String encryptedCard = jweObject.serialize();
// Step 4: Place order
Map<String, Object> orderData = new HashMap<>();
orderData.put("items", List.of(
Map.of(
"variant_id", "WS12-XS-Orange",
"quantity", 2
)
));
Map<String, String> shippingInfo = new HashMap<>();
shippingInfo.put("first_name", "John");
shippingInfo.put("last_name", "Doe");
shippingInfo.put("address1", "123 Main St");
shippingInfo.put("city", "New York");
shippingInfo.put("state_or_province", "NY");
shippingInfo.put("postal_code", "10001");
shippingInfo.put("country", "US");
shippingInfo.put("email", "john@staging.luma.gift");
shippingInfo.put("phone", "555-1234");
orderData.put("shipping_info", shippingInfo);
orderData.put("billing_info", shippingInfo); // Same as shipping
orderData.put("encrypted_card", encryptedCard);
HttpRequest orderRequest = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/api/v1/payment/domains/" + domain + "/place-order"))
.header("x-firmly-authorization", authToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
mapper.writeValueAsString(orderData)
))
.build();
HttpResponse<String> orderResponse = client.send(
orderRequest,
HttpResponse.BodyHandlers.ofString()
);
Map<String, Object> result = mapper.readValue(
orderResponse.body(),
Map.class
);
if (result.containsKey("cart_id")) {
return new CheckoutResult(
true,
result.get("cart_id").toString(),
result.get("platform_order_number").toString(),
null
);
} else {
return new CheckoutResult(
false,
null,
null,
result.get("error").toString()
);
}
}
}
// Supporting classes
class CheckoutData {
private String cardNumber;
private String cardholderName;
private String cvv;
private String expMonth;
private String expYear;
// ... getters and setters
}
class CheckoutResult {
private boolean success;
private String cartId;
private String orderNumber;
private String error;
public CheckoutResult(boolean success, String cartId,
String orderNumber, String error) {
this.success = success;
this.cartId = cartId;
this.orderNumber = orderNumber;
this.error = error;
}
// ... getters
}
// Example usage:
public static void main(String[] args) {
CheckoutData checkoutData = new CheckoutData();
checkoutData.setCardNumber("4242424242424242");
checkoutData.setCardholderName("John Doe");
checkoutData.setCvv("123");
checkoutData.setExpMonth("12");
checkoutData.setExpYear("2025");
String domain = "staging.luma.gift";
String authToken = "YOUR_AUTH_TOKEN"; // Get this from browser session
try {
CheckoutResult result = OneStepCheckout.performCheckout(
checkoutData,
domain,
authToken
);
if (result.isSuccess()) {
System.out.println("Order placed successfully! Order Number: " +
result.getOrderNumber());
} else {
System.out.println("Order failed: " + result.getError());
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
<?php
// First install: composer require web-token/jwt-framework
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256GCM;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
function oneStepCheckout($checkoutData, $domain, $authToken) {
// Step 1: Get public key in JWK format
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,
"https://cc.firmly.work/api/v1/payment/key");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-firmly-authorization: {$authToken}"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$publicKeyResponse = curl_exec($ch);
$publicKeyData = json_decode($publicKeyResponse, true);
curl_close($ch);
// Step 2: Create JWK from public key
$jwk = new JWK($publicKeyData);
// Step 3: Prepare card data
$cardData = [
'number' => $checkoutData['cardNumber'],
'name' => $checkoutData['cardholderName'],
'verification_value' => $checkoutData['cvv'],
'month' => $checkoutData['expMonth'], // e.g., "08"
'year' => $checkoutData['expYear'] // e.g., "2025"
];
// Step 4: Create JWE
$algorithmManager = new AlgorithmManager([
new RSAOAEP256(),
new A256GCM(),
]);
$compressionMethodManager = new CompressionMethodManager([
new Deflate(),
]);
$jweBuilder = new JWEBuilder(
$algorithmManager,
$algorithmManager,
$compressionMethodManager
);
$payload = json_encode($cardData);
$jwe = $jweBuilder
->create()
->withPayload($payload)
->withSharedProtectedHeader([
'alg' => 'RSA-OAEP-256',
'enc' => 'A256GCM',
'kid' => $publicKeyData['kid']
])
->addRecipient($jwk)
->build();
$serializer = new CompactSerializer();
$encryptedCard = $serializer->serialize($jwe, 0);
// Step 5: Place order
$orderData = [
'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' => 'john@staging.luma.gift',
'phone' => '555-1234'
],
'billing_info' => [
// Same structure as 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' => 'john@staging.luma.gift',
'phone' => '555-1234'
],
'encrypted_card' => $encryptedCard
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,
"https://cc.firmly.work/api/v1/payment/domains/{$domain}/place-order");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-firmly-authorization: {$authToken}",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($orderData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
curl_close($ch);
if (isset($result['cart_id'])) {
return [
'success' => true,
'cart_id' => $result['cart_id'],
'order_number' => $result['platform_order_number']
];
} else {
return [
'success' => false,
'error' => $result['error'] ?? 'Unknown error'
];
}
}
// Example usage:
$checkoutData = [
'cardNumber' => '4242424242424242',
'cardholderName' => 'John Doe',
'cvv' => '123',
'expMonth' => '12',
'expYear' => '2025'
];
$domain = 'staging.luma.gift';
$authToken = 'YOUR_AUTH_TOKEN'; // Get this from browser session
// Call the function
$result = oneStepCheckout($checkoutData, $domain, $authToken);
if ($result['success']) {
echo "Order placed successfully! Order Number: " . $result['order_number'];
} else {
echo "Order failed: " . $result['error'];
}
?>
Important Security Notes
Critical Security Requirements:
- Never store card data - Card details should only exist in memory during encryption
- Always use HTTPS - All API calls must be over secure connections
- PCI Compliance - Ensure your implementation follows PCI DSS guidelines
- Server-side encryption - Never expose encryption logic to client-side JavaScript
- 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 processedpayment_processing: Payment is being authorizedorder_completed: Order successfully placed with order detailserror: Processing failed with error details
When to Use 1-Step Checkout
- Perfect For
- Consider Alternatives If
- Single vendor stores
- Standard shipping only
- High-volume, low-complexity products
- Mobile-first experiences
- Multiple shipments needed
- Complex shipping options (scheduled delivery)
- B2B with special requirements
- Require extensive customization
Error Handling
Theplace-order API provides detailed error responses:
{
"code": 400,
"error": "InvalidAddress",
"description": "Shipping address could not be validated"
}
- 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
API Reference
Complete place-order API documentation
Payment Encryption
Learn about secure payment handling
Simple Cart Guide
Alternative multi-step approach
Testing Guide
Set up test environment
Support
Need help implementing 1-step checkout?- Integration Support: support@firmly.work
- API Status: status.firmly.work
- GitHub: Report issues