Auto-Populate Card Details From a Photo
Overview
Instead of asking a customer to manually type in their card number, expiration date, and CVV, you can let them take a photo of their physical card. Coinflow extracts the card details from the image, stores them as a token in Coinflow’s PCI-compliant vault, and returns the token so you can complete the purchase.
The flow is two steps:
- Extract the card — send the photo to
POST /tokenize/extract-card. The endpoint responds with a307redirect to Coinflow’s PCI-compliant vault proxy, so the card photo is processed there and never touches Coinflow’s servers. Following the redirect returns atokenalong with non-sensitive metadata (firstSix,lastFour,expirationMonth,expirationYear, and whether a CVV was captured). - Charge the card — pass the returned
tokenascard.cardTokentoPOST /checkout/card/{merchantId}(Card Checkout) to complete the purchase.
Access to POST /tokenize/extract-card requires that your company holds a
PCI-DSS certification. Provide your certification to your Coinflow
Integrations Representative to have the endpoint enabled for your account.
Step 1: Extract the card from the image
Send a base64-encoded photo of the card in the image field. Optionally set
mimeType (defaults to image/jpeg). Authenticate with a merchant API key that
has the ADMIN scope.
Request
POST https://api-sandbox.coinflow.cash/api/tokenize/extract-card
curl -X POST https://api-sandbox.coinflow.cash/api/tokenize/extract-card \
-H "Authorization: <apiKey>" \
-H "Content-Type: application/json" \
-d '{}'
import requests
url = "https://api-sandbox.coinflow.cash/api/tokenize/extract-card"
payload = {}
headers = {
"Authorization": "<apiKey>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
const url = 'https://api-sandbox.coinflow.cash/api/tokenize/extract-card';
const options = {
method: 'POST',
headers: {Authorization: '<apiKey>', 'Content-Type': 'application/json'},
body: '{}'
};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.coinflow.cash/api/tokenize/extract-card"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<apiKey>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
require 'uri'
require 'net/http'
url = URI("https://api-sandbox.coinflow.cash/api/tokenize/extract-card")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{}"
response = http.request(request)
puts response.read_body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
HttpResponse<String> response = Unirest.post("https://api-sandbox.coinflow.cash/api/tokenize/extract-card")
.header("Authorization", "<apiKey>")
.header("Content-Type", "application/json")
.body("{}")
.asString();
<?php
require_once('vendor/autoload.php');
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api-sandbox.coinflow.cash/api/tokenize/extract-card', [
'body' => '{}',
'headers' => [
'Authorization' => '<apiKey>',
'Content-Type' => 'application/json',
],
]);
echo $response->getBody();
using RestSharp;
var client = new RestClient("https://api-sandbox.coinflow.cash/api/tokenize/extract-card");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
import Foundation
let headers = [
"Authorization": "<apiKey>",
"Content-Type": "application/json"
]
let parameters = [] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://api-sandbox.coinflow.cash/api/tokenize/extract-card")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
The endpoint responds with a 307 Temporary Redirect. Your HTTP client must
follow the redirect and re-send the request body to the redirect location
(fetch does this automatically; for curl use --location-trusted).
The response contains the token you will use for checkout, plus metadata you
can use to pre-fill and confirm the card in your UI. cvvCaptured tells you
whether the CVV was readable from the photo — if it is false, prompt the
customer to enter their CVV manually before charging.
Response (200)
{
"token": "string",
"firstSix": "string",
"lastFour": "string",
"expirationMonth": "string",
"expirationYear": "string",
"cvvCaptured": true
}
Step 2: Charge the card with the returned token
Use the token from Step 1 as the card.cardToken field of the Card Checkout
request. This is the same endpoint used for any new-card (tokenized)
purchase.
Request
POST https://api-sandbox.coinflow.cash/api/checkout/card/{merchantId}
curl -X POST https://api-sandbox.coinflow.cash/api/checkout/card/merchantId \
-H "x-coinflow-auth-session-key: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"subtotal": {
"cents": 1,
"currency": "USD"
},
"card": {
"cardToken": "string",
"expYear": "string",
"expMonth": "string",
"email": "string",
"firstName": "string",
"lastName": "string",
"address1": "string",
"city": "string",
"country": "string"
}
}'
import requests
url = "https://api-sandbox.coinflow.cash/api/checkout/card/merchantId"
payload = {
"subtotal": {
"cents": 1,
"currency": "USD"
},
"card": {
"cardToken": "string",
"expYear": "string",
"expMonth": "string",
"email": "string",
"firstName": "string",
"lastName": "string",
"address1": "string",
"city": "string",
"country": "string"
}
}
headers = {
"x-coinflow-auth-session-key": "<apiKey>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
const url = 'https://api-sandbox.coinflow.cash/api/checkout/card/merchantId';
const options = {
method: 'POST',
headers: {'x-coinflow-auth-session-key': '<apiKey>', 'Content-Type': 'application/json'},
body: '{"subtotal":{"cents":1,"currency":"USD"},"card":{"cardToken":"string","expYear":"string","expMonth":"string","email":"string","firstName":"string","lastName":"string","address1":"string","city":"string","country":"string"}}'
};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.coinflow.cash/api/checkout/card/merchantId"
payload := strings.NewReader("{\n \"subtotal\": {\n \"cents\": 1,\n \"currency\": \"USD\"\n },\n \"card\": {\n \"cardToken\": \"string\",\n \"expYear\": \"string\",\n \"expMonth\": \"string\",\n \"email\": \"string\",\n \"firstName\": \"string\",\n \"lastName\": \"string\",\n \"address1\": \"string\",\n \"city\": \"string\",\n \"country\": \"string\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-coinflow-auth-session-key", "<apiKey>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
require 'uri'
require 'net/http'
url = URI("https://api-sandbox.coinflow.cash/api/checkout/card/merchantId")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-coinflow-auth-session-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"subtotal\": {\n \"cents\": 1,\n \"currency\": \"USD\"\n },\n \"card\": {\n \"cardToken\": \"string\",\n \"expYear\": \"string\",\n \"expMonth\": \"string\",\n \"email\": \"string\",\n \"firstName\": \"string\",\n \"lastName\": \"string\",\n \"address1\": \"string\",\n \"city\": \"string\",\n \"country\": \"string\"\n }\n}"
response = http.request(request)
puts response.read_body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
HttpResponse<String> response = Unirest.post("https://api-sandbox.coinflow.cash/api/checkout/card/merchantId")
.header("x-coinflow-auth-session-key", "<apiKey>")
.header("Content-Type", "application/json")
.body("{\n \"subtotal\": {\n \"cents\": 1,\n \"currency\": \"USD\"\n },\n \"card\": {\n \"cardToken\": \"string\",\n \"expYear\": \"string\",\n \"expMonth\": \"string\",\n \"email\": \"string\",\n \"firstName\": \"string\",\n \"lastName\": \"string\",\n \"address1\": \"string\",\n \"city\": \"string\",\n \"country\": \"string\"\n }\n}")
.asString();
<?php
require_once('vendor/autoload.php');
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api-sandbox.coinflow.cash/api/checkout/card/merchantId', [
'body' => '{
"subtotal": {
"cents": 1,
"currency": "USD"
},
"card": {
"cardToken": "string",
"expYear": "string",
"expMonth": "string",
"email": "string",
"firstName": "string",
"lastName": "string",
"address1": "string",
"city": "string",
"country": "string"
}
}',
'headers' => [
'Content-Type' => 'application/json',
'x-coinflow-auth-session-key' => '<apiKey>',
],
]);
echo $response->getBody();
using RestSharp;
var client = new RestClient("https://api-sandbox.coinflow.cash/api/checkout/card/merchantId");
var request = new RestRequest(Method.POST);
request.AddHeader("x-coinflow-auth-session-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n \"subtotal\": {\n \"cents\": 1,\n \"currency\": \"USD\"\n },\n \"card\": {\n \"cardToken\": \"string\",\n \"expYear\": \"string\",\n \"expMonth\": \"string\",\n \"email\": \"string\",\n \"firstName\": \"string\",\n \"lastName\": \"string\",\n \"address1\": \"string\",\n \"city\": \"string\",\n \"country\": \"string\"\n }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
import Foundation
let headers = [
"x-coinflow-auth-session-key": "<apiKey>",
"Content-Type": "application/json"
]
let parameters = [
"subtotal": [
"cents": 1,
"currency": "USD"
],
"card": [
"cardToken": "string",
"expYear": "string",
"expMonth": "string",
"email": "string",
"firstName": "string",
"lastName": "string",
"address1": "string",
"city": "string",
"country": "string"
]
] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://api-sandbox.coinflow.cash/api/checkout/card/merchantId")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
Response (200)
{
"paymentId": "string",
"authorizationExpiration": "string"
}
Putting it together
// 1. Extract the card from the photo
const extractResponse = await fetch(
'https://api.coinflow.cash/api/tokenize/extract-card',
{
method: 'POST',
headers: {
Authorization: MERCHANT_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
image: base64CardPhoto,
mimeType: 'image/png',
}),
}
);
const {token, expirationMonth, expirationYear, cvvCaptured} =
await extractResponse.json();
// 2. Charge the card using the returned token
const checkoutResponse = await fetch(
`https://api.coinflow.cash/api/checkout/card/${merchantId}`,
{
method: 'POST',
headers: {
Authorization: SESSION_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
card: {
cardToken: token,
expMonth: expirationMonth,
expYear: expirationYear,
// ...customer name and billing address fields
},
subtotal: {cents: 1000},
// ...remaining checkout fields
}),
}
);
If cvvCaptured is false, collect the CVV from the customer and associate
it with the token before charging so the transaction can be authorized with a
CVV.