Powered by Coinflow
Payments · Documentation
Operational

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:

  1. Extract the card — send the photo to POST /tokenize/extract-card. The endpoint responds with a 307 redirect to Coinflow’s PCI-compliant vault proxy, so the card photo is processed there and never touches Coinflow’s servers. Following the redirect returns a token along with non-sensitive metadata (firstSix, lastFour, expirationMonth, expirationYear, and whether a CVV was captured).
  2. Charge the card — pass the returned token as card.cardToken to POST /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.