Skip to main content
On this page
Integration method

Tokenize payment credentials for secure reuse.

Choose a DEUNA-hosted Vault experience or a PCI-compliant API flow, receive a reusable card ID, and process future payments safely.

Interface
DEUNA-hosted credential entry or merchant-owned PCI-compliant capture
Delivery model
SDK component or customer-authorized Cards API
Best for
Saved cards, one-click checkout, and repeat payments

Understand the credential boundary#

The card_id returned by a supported tokenization flow is a DEUNA payment-credential reference. Its availability for a later payment depends on the customer consent, token type, merchant account, provider connection, environment, country, and operation enabled for that flow.

  • Do not assume that every stored credential can move between processors or payment methods.
  • Confirm network-token, account-updater, portability, recurring-use, and deletion behavior for the target account before production.
  • Keep the underlying PAN, CVV, provider token, and private credentials out of your application logs and metadata.
  • Store the customer, consent purpose, tokenization time, credential status, and permitted future use in your system of record.
  • Support credential revocation, expiry, replacement, and customer deletion without treating a deleted token as a canceled subscription automatically.

Use the Provider capabilities catalog for published connection support and confirm account-specific credential behavior with your DEUNA Technical Account Manager.

Choose an integration method#

Payment Vault supports both DEUNA SDKs and the customer-authorized Cards API. Both methods return a DEUNA card_id; choose the method according to who owns the credential-entry experience and its Payment Card Industry Data Security Standard (PCI DSS) scope.

Integrate with the Cards API#

Use the Cards API when your application owns the card-entry experience and is prepared for the corresponding PCI DSS responsibilities. This is a customer-authorized flow: authenticate the customer, then call POST /v2/users/{user_id}/cards with that customer's Bearer token and the merchant context required by the Store a card endpoint.

:::info Supported public route For new integrations, use only POST /v2/users/{user_id}/cards. The unversioned /users/{user_id}/cards route preserves a legacy single-card contract, while /users/{user_id}/cards/{card_id}/tokens adds a processor-specific token to an existing card and is not a card-creation endpoint. Neither route is part of the current external API catalog. See Card POST route differences. :::

The /v2 request body is a JSON array, even when you store one card:

curl --request POST \
  --url 'https://api.sandbox.deuna.io/v2/users/USER_ID/cards' \
  --header 'Authorization: Bearer USER_ACCESS_TOKEN' \
  --header 'X-Merchant-ID: MERCHANT_ID' \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "card_holder": "Jane Doe",
      "card_number": "CARD_NUMBER",
      "card_cvv": "CARD_CVV",
      "expiry_month": "12",
      "expiry_year": "2029",
      "is_default": true
    }
  ]'

A successful request returns HTTP 201 Created. Store the returned data[].id as the card_id; never store the submitted PAN or CVV.

Created cardJSON
{
  "data": [
    {
      "id": "8b5d0b45-e34f-4fd3-8cb5-79a7041f86ec",
      "company": "visa",
      "last_four": "4242",
      "expiration_date": "12/29"
    }
  ]
}

After a timeout or an ambiguous response, check List stored cards before resubmitting the card. The Gateway contract does not advertise an idempotency header for this operation, so clients must not assume that a retry is idempotent. Verification metadata can be absent until DEUNA verifies the credential; see Card verification metadata.

Integrate with a DEUNA SDK#

The integration of the Payment Vault is through DEUNA's SDKs.

In web environments, the widget can be opened in a target HTML element or displayed as a modal.

Choose your SDK platform:

Features

  • The SDK exposes several callbacks that allow the merchant to react dynamically to important events within the payment flow, such as:
    • When the user closes the widget.
    • When a card or payment credential is successfully tokenized.
    • When a tokenization attempt fails.
  • The Payment Vault can be rendered embedded or as a modal.

Additional information

Learn more about Payment Vault integration:

  • Verifying event type and card brand: The Vault recognizes card brands from event metadata.
  • Discount applied if the card is Mastercard by the merchant: If the card is Mastercard, a 20% discount is applied to the current shopping cart. This action is performed entirely by your app.
  • Order Update: After applying a discount, your application is responsible for updating the order using the DEUNA Update Order endpoint.
  • Notification to the widget to update the order and installments: Changes in the status of an order are communicated using the SDK callbacks.
  • Display a promotional tag: In addition to updating the order and payment amounts, you can apply custom CSS styling to the active widget using setCustomStyles(...). See the Web SDK guide.

Process a payment with the returned card_id

To process a payment in DEUNA with given card_id use the Purchase API.

Example curl request

JavaScript
const requestBody = {
    payer_info: {
        email: "eposada@deuna.com"
    },
    payment_source: {
        method_type: "credit_card",
        card_info: {
            card_id: "8d369b6e-113a-4640-9e63-dc29957fc86a",
            installment: {
            	plan_option_id: "<uuid que se retorna en el postMessage 'onInstallmentSelected'>"
            }
        }
    },
    // ... Remaining of the request body
};

fetch('https://api.sandbox.deuna.io/v2/merchants/orders/purchase', {
    method: 'POST',
    headers: {
        'x-api-key': '<YOUR_PRIVATE_API_KEY>',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(requestBody)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
curl --location 'https://api.sandbox.deuna.io/v2/merchants/orders/purchase' \
--header 'x-api-key: YOUR_PRIVATE_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
    "payer_info": {
        "email": "foo@bar.com"
    },
    "payment_source": {
        "method_type": "credit_card",
        "card_info": {
            "card_id": "8d369b6e-113a-4640-9e63-dc29957fc86a"
        }
    },
    "order": {
        "order_id": "testapi-0003",
        "currency": "MXN",
        "items": [
          {
              "id": "216",
              "name": "10 Cellphones",
              "description": "",
              "options": "string option",
              "total_amount": {
                  "original_amount": 2000,
                  "amount": 2000,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "unit_price": {
                  "amount": 200,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "tax_amount": {
                  "amount": 0,
                  "currency": "MXN",
                  "currency_symbol": "$"
              },
              "quantity": 1,
              "uom": "string",
              "upc": "string",
              "sku": "",
              "isbn": "",
              "brand": "",
              "manufacturer": "",
              "category": "",
              "color": "",
              "size": "",
              "weight": {
                  "amount": 0,
                  "unit": "kg"
              },
              "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/db5b698c57654116_domicilio_216_750x750_1662162887.png?d=200x200&format=webp",
              "details_url": "",
              "type": "physcal",
              "taxable": true
          }
        ],
        "sub_total": 2000,
        "total_amount": 2000,
        "store_code": "all",
        "billing_address": {
            "address1": "presa angostura 36PH",
            "address2": "",
            "address_type": "home",
            "city": "CDMX",
            "country_code": "MX",
            "email": "foo@bar.com",
            "first_name": "efren",
            "identity_document": "162915134",
            "is_default": true,
            "last_name": "garcia",
            "phone": "+525222222222",
            "state_code": "MX",
            "state_name": "miguel hidalgo",
            "zipcode": "11500"
        }
    }
}'
PHP
$curl = curl_init();

$requestBody = json_encode(array(
    "payer_info" => array(
        "email" => "foo@bar.com"
    ),
    "payment_source" => array(
        "method_type" => "credit_card",
        "card_info" => array(
            "card_id" => "8d369b6e-113a-4640-9e63-dc29957fc86a"
        )
    ),
    // Aquí puedes continuar añadiendo el resto del cuerpo de la solicitud
    "order" => array(
        "order_id" => "testapi-0003",
        "currency" => "MXN",
        "items" => array(
            // Aquí van los detalles de los items
        ),
        // Continúa con el resto de los detalles del pedido
    )
));

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://api.sandbox.deuna.io/v2/merchants/orders/purchase",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => $requestBody, // Aquí se utiliza la variable
  CURLOPT_HTTPHEADER => array(
    "x-api-key: YOUR_PRIVATE_API_KEY", // Asegúrate de reemplazar con tu API key real
    "Content-Type: application/json"
  ),
));


$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
Ruby
require 'uri'
require 'net/http'
require 'json'

url = URI("https://api.sandbox.deuna.io/v2/merchants/orders/purchase")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request_body = {
    payer_info: {
        email: "foo@bar.com"
    },
    payment_source: {
        method_type: "credit_card",
        card_info: {
            card_id: "8d369b6e-113a-4640-9e63-dc29957fc86a"
        }
    }
    # ... Agrega aquí el resto del cuerpo de la solicitud
}.to_json

request = Net::HTTP::Post.new(url)
request["x-api-key"] = "YOUR_PRIVATE_API_KEY" # Reemplaza con tu API key
request["Content-Type"] = "application/json"
request.body = request_body

response = http.request(request)
puts response.read_body
Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;

public class Main {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        
        JSONObject requestBody = new JSONObject()
            .put("payer_info", new JSONObject().put("email", "foo@bar.com"))
            .put("payment_source", new JSONObject()
                .put("method_type", "credit_card")
                .put("card_info", new JSONObject().put("card_id", "8d369b6e-113a-4640-9e63-dc29957fc86a")))
            // ... Agrega aquí el resto del cuerpo de la solicitud

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.sandbox.deuna.io/v2/merchants/orders/purchase"))
            .header("x-api-key", "YOUR_PRIVATE_API_KEY") // Reemplaza con tu API key
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
            .build();

        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();
    }
}

Demo

After integrating the Payment Vault, the experience is similar to the following demo: