Direct API Integration
When to use direct integration
Section titled “When to use direct integration”Direct API integration gives you full control over the payment UI — useful for native mobile apps or highly custom checkout flows. If you handle raw card numbers, your integration becomes in-scope for PCI DSS. See Security & PCI Compliance for details.
For most web integrations, the widget handles card data and keeps you out of PCI scope.
Create and confirm in sequence
Section titled “Create and confirm in sequence”Use a tokenised pm_xxx ID to create and immediately confirm a payment intent server-side:
# 1. Create payment intentcurl -X POST https://api.elasticpay.co/api/v1/payment_intents \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"amount": 3500, "currency": "AUD"}'
# 2. Confirm with tokenised payment methodcurl -X POST https://api.elasticpay.co/api/v1/payment_intents/pi_0abc123/confirm \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"payment_method": "pm_0xyz789abc123def456ghi012jkl"}'const BASE = "https://api.elasticpay.co";const AUTH = "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 1. Create payment intentconst createRes = await fetch(`${BASE}/api/v1/payment_intents`, { method: "POST", headers: { "Authorization": AUTH, "Content-Type": "application/json" }, body: JSON.stringify({ amount: 3500, currency: "AUD" }),});const { id } = await createRes.json();
// 2. Confirm with tokenised payment methodconst confirmRes = await fetch( `${BASE}/api/v1/payment_intents/${id}/confirm`, { method: "POST", headers: { "Authorization": AUTH, "Content-Type": "application/json" }, body: JSON.stringify({ payment_method: "pm_0xyz789abc123def456ghi012jkl" }), });const result = await confirmRes.json();import requests
BASE = "https://api.elasticpay.co"HEADERS = { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json",}
# 1. Create payment intentpi = requests.post( f"{BASE}/api/v1/payment_intents", headers=HEADERS, json={"amount": 3500, "currency": "AUD"},).json()
# 2. Confirm with tokenised payment methodresult = requests.post( f"{BASE}/api/v1/payment_intents/{pi['id']}/confirm", headers=HEADERS, json={"payment_method": "pm_0xyz789abc123def456ghi012jkl"},).json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
// 1. Create payment intent$createRes = $client->post('/api/v1/payment_intents', [ 'json' => ['amount' => 3500, 'currency' => 'AUD'],]);$pi = json_decode($createRes->getBody(), true);
// 2. Confirm with tokenised payment method$confirmRes = $client->post('/api/v1/payment_intents/' . $pi['id'] . '/confirm', [ 'json' => ['payment_method' => 'pm_0xyz789abc123def456ghi012jkl'],]);$result = json_decode($confirmRes->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
# 1. Create payment intentcreate_res = conn.post('/api/v1/payment_intents') do |req| req.body = { amount: 3500, currency: 'AUD' }endpi_id = create_res.body['id']
# 2. Confirm with tokenised payment methodresult = conn.post("/api/v1/payment_intents/#{pi_id}/confirm") do |req| req.body = { payment_method: 'pm_0xyz789abc123def456ghi012jkl' }endresult = result.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
// 1. Create payment intentvar createRes = await http.PostAsJsonAsync("/api/v1/payment_intents", new { amount = 3500, currency = "AUD" });var pi = await createRes.Content.ReadFromJsonAsync<JsonElement>();var piId = pi.GetProperty("id").GetString();
// 2. Confirm with tokenised payment methodvar confirmRes = await http.PostAsJsonAsync( $"/api/v1/payment_intents/{piId}/confirm", new { payment_method = "pm_0xyz789abc123def456ghi012jkl" });var result = await confirmRes.Content.ReadFromJsonAsync<JsonElement>();Idempotency
Section titled “Idempotency”Add an Idempotency-Key header to safely retry requests without risk of double-charging:
curl -X POST https://api.elasticpay.co/api/v1/payment_intents \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order_9a8b7c6d-unique-key-here" \ -d '{"amount": 3500, "currency": "AUD"}'async function createPaymentWithRetry(amount, idempotencyKey) { const response = await fetch( "https://api.elasticpay.co/api/v1/payment_intents", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ amount, currency: "AUD" }), } ); if (!response.ok) { const error = await response.json(); throw new Error(error.error?.message ?? "Request failed"); } return response.json();}import requestsimport uuid
def create_payment_with_retry(amount, idempotency_key): res = requests.post( "https://api.elasticpay.co/api/v1/payment_intents", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", "Idempotency-Key": idempotency_key, }, json={"amount": amount, "currency": "AUD"}, ) res.raise_for_status() return res.json()
# Generate once; reuse on retrykey = str(uuid.uuid4())payment = create_payment_with_retry(3500, key)<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
function createPaymentWithRetry($client, int $amount, string $idempotencyKey): array { $res = $client->post('/api/v1/payment_intents', [ 'headers' => ['Idempotency-Key' => $idempotencyKey], 'json' => ['amount' => $amount, 'currency' => 'AUD'], ]); return json_decode($res->getBody(), true);}
$key = uniqid('order_', true);$payment = createPaymentWithRetry($client, 3500, $key);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
def create_payment_with_retry(conn, amount, idempotency_key) res = conn.post('/api/v1/payment_intents') do |req| req.headers['Idempotency-Key'] = idempotency_key req.body = { amount: amount, currency: 'AUD' } end res.bodyend
key = SecureRandom.uuidpayment = create_payment_with_retry(conn, 3500, key)using System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
async Task<JsonElement> CreatePaymentWithRetry(int amount, string idempotencyKey){ var req = new HttpRequestMessage(HttpMethod.Post, "/api/v1/payment_intents") { Content = JsonContent.Create(new { amount, currency = "AUD" }), }; req.Headers.Add("Idempotency-Key", idempotencyKey); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode(); return await res.Content.ReadFromJsonAsync<JsonElement>();}
var key = Guid.NewGuid().ToString();var payment = await CreatePaymentWithRetry(3500, key);- The same key returns the same response within the TTL window
- Default TTL: 900 seconds; maximum: 1800 seconds
- Keys are scoped per biller account
- Use a UUID or a unique order/transaction identifier as the key
Those rules are for payment intents. A payment plan’s schedule changes have their own, stricter contract (Changing a plan’s schedule):
| Payment intents | Payment-plan schedule changes | |
|---|---|---|
Idempotency-Key |
Optional | Required: 400 missing_idempotency_key without one |
| Scope | Your biller account | Your biller account and the API key or OAuth connection that sent it |
| Replay window | Idempotency-TTL: 900 seconds by default, 1800 at most |
24 hours |
| The key reused for a different request | 409 |
409 idempotency_key_conflict |
| The key’s request still running | 409 |
409 idempotency_key_in_progress: retry with the same key |