1-Step Checkout
Why 1-Step Checkout?
Firmly’s full Cart API walks through several steps:
- Add items (the cart is created implicitly) → 2. Set shipping → 3. Get merchant-calculated tax & totals → 4. Process payment → 5. Complete order
With Firmly’s place-order API, you can do all of this in one single API call.
Benefits
How It Works
The place-order API combines cart creation, item addition, shipping calculation, and payment processing into a single atomic operation:
Quick Implementation
Collect Customer Information
Gather items, shipping address, and credit card details in your checkout form
Encrypt Credit Card
Use Firmly’s public key to JWE-encrypt card details for secure transmission
Call place-order API
Submit everything in one API call and handle the response
Credit Card Encryption
The credit card data must be encrypted as a JWE token containing these fields:
| Field | Description | Example |
|---|---|---|
number |
Card number (PAN) | 4111111111111111 |
name |
Cardholder name | John Smith |
verification_value |
CVV/CVC | 123 |
month |
Expiry month (2 digits) | 08 |
year |
Expiry year (4 digits) | 2028 |
The plaintext object encrypted into the JWE looks like this:
{"number": "4111111111111111","name": "John Smith","verification_value": "123","month": "08","year": "2028"}
Complete Example
Here’s a full implementation of 1-step checkout.
// First, install the jose library: npm install joseimport * as jose from 'jose';async function oneStepCheckout(checkoutData, domain, authToken) {// Step 1: Get public key for encryptionconst 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 JWEconst cardData = {number: checkoutData.cardNumber,name: checkoutData.cardholderName,verification_value: checkoutData.cvv,month: checkoutData.expMonth, // e.g., "08"year: checkoutData.expYear // e.g., "2028"};// Import the public keyconst publicKey = await jose.importJWK(publicKeyJWK, 'RSA-OAEP-256');// Create JWE encrypted cardconst 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 callconst orderResponse = await fetch(`https://cc.firmly.work/api/v2/payment/domains/${domain}/place-order`,{method: 'POST',headers: {'x-firmly-authorization': authToken,'Content-Type': 'application/json'},body: JSON.stringify({items: [{add_to_cart_ref: { 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 (billing_info is required)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 responseconst result = await orderResponse.json();// Success is signalled by cart_status === "submitted",// NOT by the mere presence of cart_id.if (result.cart_status === 'submitted') {// Success! Order completedreturn {success: true,cartId: result.cart_id,orderNumber: result.platform_order_number};} else {// Handle errorreturn {success: false,error: result.error};}}// Example usage:const checkoutData = {cardNumber: '4111111111111111',cardholderName: 'John Doe',cvv: '123',expMonth: '12',expYear: '2028'};const domain = 'staging.luma.gift';const authToken = 'YOUR_AUTH_TOKEN'; // Get this from browser session// Call the functiononeStepCheckout(checkoutData, domain, authToken).then(result => {if (result.success) {console.log('Order placed successfully!', result.orderNumber);} else {console.error('Order failed:', result.error);}});
import requestsimport jsonfrom joserfc.jwe import encrypt_compactfrom joserfc.jwk import RSAKeydef 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 datacard_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., "2028"}# Step 3: Encrypt using JWE with JWK# Import the JWKpublic_key = RSAKey.import_key(public_key_jwk)# Create JWE encrypted card. joserfc's signature is# encrypt_compact(protected, plaintext, public_key):# protected header first, plaintext second, key third.protected_header = {"alg": "RSA-OAEP-256","enc": "A256GCM","kid": public_key_jwk['kid'] # Get kid from JWK response}encrypted_card = encrypt_compact(protected_header,json.dumps(card_data).encode('utf-8'),public_key)# Step 4: Place orderorder_data = {'items': [{'add_to_cart_ref': {'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/v2/payment/domains/{domain}/place-order",headers={'x-firmly-authorization': auth_token,'Content-Type': 'application/json'},json=order_data)result = response.json()# Success is signalled by cart_status == "submitted",# NOT by the mere presence of cart_id.if result.get('cart_status') == 'submitted':return {'success': True,'cart_id': result.get('cart_id'),'order_number': result.get('platform_order_number')}else:return {'success': False,'error': result.get('error')}# Example usage:checkout_data = {'card_number': '4111111111111111','cardholder_name': 'John Doe','cvv': '123','exp_month': '12','exp_year': '2028'}domain = 'staging.luma.gift'auth_token = 'YOUR_AUTH_TOKEN' # Get this from browser session# Call the functionresult = 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.2import 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 formatHttpClient 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 dataMap<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., "2028"// Step 3: Encrypt using JWERSAKey rsaKey = RSAKey.parse(mapper.writeValueAsString(jwkData));// Create JWE objectJWEHeader 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);// EncryptjweObject.encrypt(new RSAEncrypter(rsaKey));String encryptedCard = jweObject.serialize();// Step 4: Place orderMap<String, Object> orderData = new HashMap<>();orderData.put("items", List.of(Map.of("add_to_cart_ref", 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 shippingorderData.put("encrypted_card", encryptedCard);HttpRequest orderRequest = HttpRequest.newBuilder().uri(URI.create(API_BASE + "/api/v2/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);// Success is signalled by cart_status == "submitted",// NOT by the mere presence of cart_id.if ("submitted".equals(String.valueOf(result.get("cart_status")))) {return new CheckoutResult(true,String.valueOf(result.get("cart_id")),String.valueOf(result.get("platform_order_number")),null);} else {return new CheckoutResult(false,null,null,result.get("error").toString());}}}// Supporting classesclass 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 class Main {public static void main(String[] args) {CheckoutData checkoutData = new CheckoutData();checkoutData.setCardNumber("4111111111111111");checkoutData.setCardholderName("John Doe");checkoutData.setCvv("123");checkoutData.setExpMonth("12");checkoutData.setExpYear("2028");String domain = "staging.luma.gift";String authToken = "YOUR_AUTH_TOKEN"; // Get this from browser sessiontry {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-frameworkuse 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., "2028"];// 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' => [['add_to_cart_ref' => ['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/v2/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);// Success is signalled by cart_status === "submitted",// NOT by the mere presence of cart_id.if (isset($result['cart_status']) && $result['cart_status'] === 'submitted') {return ['success' => true,'cart_id' => $result['cart_id'] ?? null,'order_number' => $result['platform_order_number'] ?? null];} else {return ['success' => false,'error' => $result['error'] ?? 'Unknown error'];}}// Example usage:$checkoutData = ['cardNumber' => '4111111111111111','cardholderName' => 'John Doe','cvv' => '123','expMonth' => '12','expYear' => '2028'];$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
Real-Time Progress with SSE
Using SSE for Live Updates
// Option 1: Use SSE by setting Accept headerconst domain = 'staging.luma.gift';const authToken = 'YOUR_AUTH_TOKEN'; // Get this from browser sessionconst orderData = {items: [{ add_to_cart_ref: { variant_id: 'WS12-XS-Orange' }, quantity: 1 }],shipping_info: { /* ... */ },billing_info: { /* ... */ },encrypted_card: encryptedCard};const response = await fetch(`https://cc.firmly.work/api/v2/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 streamconst 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 chunkconsole.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
- 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
The place-order API provides detailed error responses:
{"code": 400,"error": "InvalidShippingInfo","description": "Shipping address could not be validated"}
The error envelope is { code, error, description } — code is the numeric HTTP status and error is the PascalCase wire name from the error catalog. Common error scenarios:
- InvalidShippingInfo: Address validation failed (
InvalidInputBodyfor malformed request bodies) - NotEnoughStockError: Product not available (409)
- CreditCardDeclined: Card declined (422)
- CreditCardInvalidNumber / CreditCardInvalidExpiry / CreditCardInvalidSecurityCode: Card details failed validation (422)
Next Steps
Support
Need help implementing 1-step checkout? Contact Firmly.