View as Markdown

llms.txt

No such blueprint

This instance publishes no blueprint at that address. The catalog lists every one it does publish.

Back to the blueprints

Blueprints Full integrations

Save a card and charge it later

Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a second order without the card number, and retire it when the customer is done with you.

Signed in, you can run this blueprint against your own sandbox merchant one call at a time, with the values from each call threaded into the next. Sign in to run it.

6 steps, 5 API callsPaymentsTokenizationTransactions

Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.

Take the first payment

1. Charge the card the first time

API call

POST /api/transactions

An ordinary card sale, with the card number on the request. This is the one and only time the card number crosses your system in this flow: everything after it addresses the card by a handle. Keep the transaction id and the merchant id off the response, because the tokenization below is scoped by both.

Reference for this operation

Values this step gives you

  • {{transactionId}} The id of the created sale, from the response body's id property.
  • {{merchantId}} The merchant the sale belongs to, from the response body's merchantId property.
cURL

curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
.NET

using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        cvv = 123
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

response.EnsureSuccessStatusCode();

var sale = await response.Content.ReadFromJsonAsync<JsonElement>();
var transactionId = sale.GetProperty("id").GetString();
var merchantId = sale.GetProperty("merchantId").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "transactionType": "Sale",
  "resultCode": "Ok",
  "authorizedAmount": 10.00,
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "Approved"
  }
}

Save the card

2. Turn that sale into a reusable stored payment method

API call

POST /api/tokens/create-from-transaction-async?transactionId={{transactionId}}&merchantId={{merchantId}}

Mint a token from the card the sale was paid with. Nothing sensitive travels in either direction. The request carries two ids, and the response carries the handle plus masked details for you to show the customer, such as "Visa ending 1111" in a wallet list. Store the publicReference against your customer record and nothing else about the card. Do this only when the customer has agreed you may keep their card, and keep a record of that agreement. Storing a card is a promise to them before it's an API call.

Reference for this operation

Values this step gives you

  • {{savedCardToken}} The opaque handle for the stored payment method, from the response body's publicReference property. This is the value your system stores and the value a later charge sends.
cURL

curl -X POST \
  "{{BASE_URL}}/api/tokens/create-from-transaction-async?transactionId={{transactionId}}&merchantId={{merchantId}}" \
  -H "api-key: {{API_KEY}}"
.NET

var minted = await http.PostAsync(
    "/api/tokens/create-from-transaction-async"
    + $"?transactionId={transactionId}&merchantId={merchantId}",
    content: null);

minted.EnsureSuccessStatusCode();

var token = await minted.Content.ReadFromJsonAsync<JsonElement>();

// The opaque pt_ handle: store this against your customer and nothing else about
// the card. The masked details beside it are for showing the customer which card
// they picked, not for reconstructing one.
var savedCardToken = token.GetProperty("publicReference").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "4e6f8a0b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",
  "publicReference": "pt_7Qh2Kd4RmT9xLbVn",
  "type": "Card",
  "category": "Internal",
  "maskedPaymentDetails": {
    "paymentMethodType": "Card"
  }
}

Charge it again later

3. Charge the stored payment method without the card number

API call

POST /api/transactions

Send the same create call as the first sale, with tokenData in place of cardData and no card number anywhere. The amount is free to differ (9.00 here rather than 10.00), because a stored payment method is a stored instrument rather than a stored amount. Send initiationType as well, and choose it by whether the customer is present for this charge, not by which system sends it. This step is the customer at your checkout picking the card they saved and selecting Pay, so it's CardholderInitiated: the platform reports it to the processor as a card-on-file use and needs nothing else from you. A charge the customer isn't sitting in front of, such as a subscription renewal or a balance you collect later, is MerchantInitiated instead, with a mitReason and the owning customer in invoiceData.customerId, and it's accepted only against a stored-credential consent captured when the card was saved on a hosted payment page. That's a different collection story from this one, so this step doesn't demonstrate it. The guide linked below shows both shapes side by side.

Reference for this operation

Values this step gives you

    cURL

    curl -X POST "{{BASE_URL}}/api/transactions" \
      -H "api-key: {{API_KEY}}" \
      -H "Content-Type: application/json" \
      -d '{
        "transactionType": "Sale",
        "initiationType": "CardholderInitiated",
        "tokenData": {
          "token": "{{savedCardToken}}"
        },
        "invoiceData": {
          "amounts": { "base": 9.00, "total": 9.00 }
        }
      }'
    .NET

    var repeat = await http.PostAsJsonAsync("/api/transactions", new
    {
        transactionType = "Sale",
        initiationType = "CardholderInitiated",
        tokenData = new { token = savedCardToken },
        invoiceData = new
        {
            amounts = new { @base = 9.00m, total = 9.00m }
        }
    });
    
    repeat.EnsureSuccessStatusCode();

    Retire the stored payment method

    4. Look the stored payment method up by its handle

    API call

    POST /api/tokens/resolve-payment-token-async?paymentTokenidentifier={{savedCardToken}}&transactionMerchantId={{merchantId}}&doOwnershipCheck=true

    Resolve the handle to the stored record. Two things come back that matter: the masked details, which let you render a wallet list from handles alone, and the record's own id, which is what the deactivation below addresses. Keep sending doOwnershipCheck=true, which is what makes the resolution refuse a handle belonging to another merchant. The record id is neither a charge handle nor the value to store. Look it up when you need it, and keep storing the publicReference.

    Reference for this operation

    Values this step gives you

    • {{storedTokenId}} The id of the stored record, from the response body's id property. Used by the deactivation below and not stored anywhere.
    cURL

    curl -X POST \
      "{{BASE_URL}}/api/tokens/resolve-payment-token-async?paymentTokenidentifier={{savedCardToken}}&transactionMerchantId={{merchantId}}&doOwnershipCheck=true" \
      -H "api-key: {{API_KEY}}"
    .NET

    var lookup = await http.PostAsync(
        "/api/tokens/resolve-payment-token-async"
        + $"?paymentTokenidentifier={savedCardToken}"
        + $"&transactionMerchantId={merchantId}&doOwnershipCheck=true",
        content: null);
    
    lookup.EnsureSuccessStatusCode();
    
    var stored = await lookup.Content.ReadFromJsonAsync<JsonElement>();
    var storedTokenId = stored.GetProperty("id").GetString();
    var maskedCard = stored.GetProperty("paymentDetails");

    What this step answers with

    Abridged to the properties this step depends on. A real response carries more.

    HTTP 200

    {
      "id": "4e6f8a0b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",
      "publicReference": "{{savedCardToken}}",
      "merchantId": "{{merchantId}}",
      "transactionId": "{{transactionId}}",
      "type": "Card",
      "category": "Internal",
      "status": "Active",
      "label": "Visa ending 1111",
      "paymentDetails": {
        "paymentMethodType": "Card"
      }
    }

    5. Retire the stored payment method

    API call

    POST /api/tokens/deactivate-async?id={{storedTokenId}}

    Deactivate the token when the customer removes their card, closes their account, or asks you to forget them. The record survives, so the transactions that referenced it still reconcile, and further charges against the handle are refused. Prefer this to deleting. Deletion is refused outright once any transaction references the token, which is every token that was ever used. Build this path now rather than the day you need it.

    Reference for this operation

    Values this step gives you

      cURL

      curl -X POST "{{BASE_URL}}/api/tokens/deactivate-async?id={{storedTokenId}}" \
        -H "api-key: {{API_KEY}}"
      .NET

      var retired = await http.PostAsync(
          $"/api/tokens/deactivate-async?id={storedTokenId}", content: null);
      
      retired.EnsureSuccessStatusCode();
      
      var result = await retired.Content.ReadFromJsonAsync<JsonElement>();
      var status = result.GetProperty("status").GetString(); // Inactive

      6. If you need to save a card without charging it

      On your side

      This blueprint saves the card it just charged, which is the case an integration that already takes payments grows into. A signup that stores a card before the first order has no sale to mint from, and collecting the number yourself to vault it puts you back in scope for handling it. The hosted payment page has a no-charge save mode for exactly that. The customer enters the card on a page you don't host, and you get the same handle back. The hosted page is also where the customer's stored-credential consent is recorded, which a later charge made without them present has to have. The blueprint below covers the hosted flow end to end.

      Reference for this operation

      Values this step gives you

        Run the whole flow as one script

        Every step above in one script you can copy and run. Replace {{API_KEY}} with your own API key and {{BASE_URL}} with this instance's API address, and set any value the script asks you for at the top. A step that happens outside the API stays a comment.

        Code sample language

        brew install jq

        cURL

        #!/usr/bin/env bash
        # Save a card and charge it later
        #
        # Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a
        # second order without the card number, and retire it when the customer is done with you.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        set -euo pipefail
        
        BASE_URL="{{BASE_URL}}"
        API_KEY="{{API_KEY}}"
        
        # Sends one request and prints the response body. A failed call prints the API's answer and stops
        # the script.
        call() {
          local method="$1" path="$2" body="${3:-}" out
          local args=(-sS --fail-with-body -X "$method" "$BASE_URL$path" -H "api-key: $API_KEY")
          if [ -n "$body" ]; then
            args+=(-H "Content-Type: application/json" -d "$body")
          fi
          if ! out=$(curl "${args[@]}"); then
            printf '%s\n' "$out" >&2
            return 1
          fi
          printf '%s' "$out"
        }
        
        # Percent-encodes a value for use in a URL.
        urlencode() {
          jq -rn --arg value "$1" '$value | @uri'
        }
        
        # Phase 1: Take the first payment
        
        # Step 1: Charge the card the first time
        step1=$(call POST "/api/transactions" '{
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        }')
        transactionId=$(jq -er '.id | if . == null then error("The response carried no value for transactionId.") else tostring end' <<< "$step1")
        merchantId=$(jq -er '.merchantId | if . == null then error("The response carried no value for merchantId.") else tostring end' <<< "$step1")
        
        # Phase 2: Save the card
        
        # Step 2: Turn that sale into a reusable stored payment method
        step2=$(call POST "/api/tokens/create-from-transaction-async?transactionId=$(urlencode "$transactionId")&merchantId=$(urlencode "$merchantId")")
        savedCardToken=$(jq -er '.publicReference | if . == null then error("The response carried no value for savedCardToken.") else tostring end' <<< "$step2")
        
        # Phase 3: Charge it again later
        
        # Step 3: Charge the stored payment method without the card number
        body=$(jq -n --arg savedCardToken "$savedCardToken" '{
          "transactionType": "Sale",
          "initiationType": "CardholderInitiated",
          "tokenData": {
            "token": $savedCardToken
          },
          "invoiceData": {
            "amounts": { "base": 9.00, "total": 9.00 }
          }
        }')
        call POST "/api/transactions" "$body" > /dev/null
        
        # Phase 4: Retire the stored payment method
        
        # Step 4: Look the stored payment method up by its handle
        step4=$(call POST "/api/tokens/resolve-payment-token-async?paymentTokenidentifier=$(urlencode "$savedCardToken")&transactionMerchantId=$(urlencode "$merchantId")&doOwnershipCheck=true")
        storedTokenId=$(jq -er '.id | if . == null then error("The response carried no value for storedTokenId.") else tostring end' <<< "$step4")
        
        # Step 5: Retire the stored payment method
        call POST "/api/tokens/deactivate-async?id=$(urlencode "$storedTokenId")" > /dev/null
        
        # Step 6: If you need to save a card without charging it
        # This blueprint saves the card it just charged, which is the case an integration that already takes
        # payments grows into. A signup that stores a card before the first order has no sale to mint from,
        # and collecting the number yourself to vault it puts you back in scope for handling it. The hosted
        # payment page has a no-charge save mode for exactly that. The customer enters the card on a page
        # you don't host, and you get the same handle back. The hosted page is also where the customer's
        # stored-credential consent is recorded, which a later charge made without them present has to have.
        # The blueprint below covers the hosted flow end to end.
        

        PowerShell

        # Save a card and charge it later
        #
        # Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a
        # second order without the card number, and retire it when the customer is done with you.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        $ErrorActionPreference = 'Stop'
        
        $baseUrl = '{{BASE_URL}}'
        $apiKey = '{{API_KEY}}'
        
        # Sends one request and returns the parsed response body. A failed call stops the script.
        function Invoke-BlueprintCall([string] $Method, [string] $Path, [string] $Body) {
            $arguments = @{
                Method  = $Method
                Uri     = $baseUrl + $Path
                Headers = @{ 'api-key' = $apiKey }
            }
            if ($Body) {
                $arguments.ContentType = 'application/json'
                $arguments.Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
            }
            Invoke-RestMethod @arguments
        }
        
        # Turns a value read off a response back into the text the API sent. A missing value stops the
        # script.
        function ConvertTo-CaptureValue($Value, [string] $Name) {
            if ($null -eq $Value) { throw "The response carried no value for $Name." }
            if ($Value -is [datetime] -and $Value.Kind -eq 'Unspecified') { return $Value.ToString('yyyy-MM-ddTHH:mm:ss.FFFFFFF', [cultureinfo]::InvariantCulture) }
            if ($Value -is [datetime]) { return $Value.ToUniversalTime().ToString('o') }
            if ($Value -is [bool]) { return $Value.ToString().ToLowerInvariant() }
            [string]$Value
        }
        
        # Phase 1: Take the first payment
        
        # Step 1: Charge the card the first time
        $body = @'
        {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        }
        '@
        $step1 = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body
        $transactionId = ConvertTo-CaptureValue $step1.id 'transactionId'
        $merchantId = ConvertTo-CaptureValue $step1.merchantId 'merchantId'
        
        # Phase 2: Save the card
        
        # Step 2: Turn that sale into a reusable stored payment method
        $step2 = Invoke-BlueprintCall -Method 'POST' -Path "/api/tokens/create-from-transaction-async?transactionId=$([uri]::EscapeDataString($transactionId))&merchantId=$([uri]::EscapeDataString($merchantId))"
        $savedCardToken = ConvertTo-CaptureValue $step2.publicReference 'savedCardToken'
        
        # Phase 3: Charge it again later
        
        # Step 3: Charge the stored payment method without the card number
        $body = @"
        {
          "transactionType": "Sale",
          "initiationType": "CardholderInitiated",
          "tokenData": {
            "token": $(ConvertTo-Json -InputObject ([string]($savedCardToken)))
          },
          "invoiceData": {
            "amounts": { "base": 9.00, "total": 9.00 }
          }
        }
        "@
        $null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body
        
        # Phase 4: Retire the stored payment method
        
        # Step 4: Look the stored payment method up by its handle
        $step4 = Invoke-BlueprintCall -Method 'POST' -Path "/api/tokens/resolve-payment-token-async?paymentTokenidentifier=$([uri]::EscapeDataString($savedCardToken))&transactionMerchantId=$([uri]::EscapeDataString($merchantId))&doOwnershipCheck=true"
        $storedTokenId = ConvertTo-CaptureValue $step4.id 'storedTokenId'
        
        # Step 5: Retire the stored payment method
        $null = Invoke-BlueprintCall -Method 'POST' -Path "/api/tokens/deactivate-async?id=$([uri]::EscapeDataString($storedTokenId))"
        
        # Step 6: If you need to save a card without charging it
        # This blueprint saves the card it just charged, which is the case an integration that already takes
        # payments grows into. A signup that stores a card before the first order has no sale to mint from,
        # and collecting the number yourself to vault it puts you back in scope for handling it. The hosted
        # payment page has a no-charge save mode for exactly that. The customer enters the card on a page
        # you don't host, and you get the same handle back. The hosted page is also where the customer's
        # stored-credential consent is recorded, which a later charge made without them present has to have.
        # The blueprint below covers the hosted flow end to end.
        

        TypeScript

        // Save a card and charge it later
        //
        // Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a
        // second order without the card number, and retire it when the customer is done with you.
        //
        // Every API call in this blueprint, in order. Each value a call returns is passed to the calls
        // after it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        export {};
        
        const baseUrl = '{{BASE_URL}}';
        const apiKey = '{{API_KEY}}';
        
        // Sends one request and returns the parsed response body. A failed call throws.
        async function call(method: string, path: string, body?: unknown): Promise<any> {
          const headers: Record<string, string> = { 'api-key': apiKey };
          if (body !== undefined) {
            headers['Content-Type'] = 'application/json';
          }
        
          const response = await fetch(baseUrl + path, {
            method,
            headers,
            body: body === undefined ? undefined : JSON.stringify(body),
          });
        
          const text = await response.text();
          if (!response.ok) {
            throw new Error(`${method} ${path} answered ${response.status}: ${text}`);
          }
        
          return text ? JSON.parse(text) : null;
        }
        
        // Turns a value read off a response into the text the API sent. A missing value throws.
        function capture(value: unknown, name: string): string {
          if (value === undefined || value === null) {
            throw new Error(`The response carried no value for ${name}.`);
          }
        
          return typeof value === 'string' ? value : JSON.stringify(value);
        }
        
        // Phase 1: Take the first payment
        
        // Step 1: Charge the card the first time
        const step1 = await call('POST', '/api/transactions', {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        });
        const transactionId = capture(step1.id, 'transactionId');
        const merchantId = capture(step1.merchantId, 'merchantId');
        
        // Phase 2: Save the card
        
        // Step 2: Turn that sale into a reusable stored payment method
        const step2 = await call('POST', `/api/tokens/create-from-transaction-async?transactionId=${encodeURIComponent(transactionId)}&merchantId=${encodeURIComponent(merchantId)}`);
        const savedCardToken = capture(step2.publicReference, 'savedCardToken');
        
        // Phase 3: Charge it again later
        
        // Step 3: Charge the stored payment method without the card number
        await call('POST', '/api/transactions', {
          "transactionType": "Sale",
          "initiationType": "CardholderInitiated",
          "tokenData": {
            "token": savedCardToken
          },
          "invoiceData": {
            "amounts": { "base": 9.00, "total": 9.00 }
          }
        });
        
        // Phase 4: Retire the stored payment method
        
        // Step 4: Look the stored payment method up by its handle
        const step4 = await call('POST', `/api/tokens/resolve-payment-token-async?paymentTokenidentifier=${encodeURIComponent(savedCardToken)}&transactionMerchantId=${encodeURIComponent(merchantId)}&doOwnershipCheck=true`);
        const storedTokenId = capture(step4.id, 'storedTokenId');
        
        // Step 5: Retire the stored payment method
        await call('POST', `/api/tokens/deactivate-async?id=${encodeURIComponent(storedTokenId)}`);
        
        // Step 6: If you need to save a card without charging it
        // This blueprint saves the card it just charged, which is the case an integration that already
        // takes payments grows into. A signup that stores a card before the first order has no sale to mint
        // from, and collecting the number yourself to vault it puts you back in scope for handling it. The
        // hosted payment page has a no-charge save mode for exactly that. The customer enters the card on a
        // page you don't host, and you get the same handle back. The hosted page is also where the
        // customer's stored-credential consent is recorded, which a later charge made without them present
        // has to have. The blueprint below covers the hosted flow end to end.
        

        C#

        // Save a card and charge it later
        //
        // Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a
        // second order without the card number, and retire it when the customer is done with you.
        //
        // Every API call in this blueprint, in order. Each value a call returns is passed to the calls
        // after it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        using System.Text;
        using System.Text.Json;
        
        var baseUrl = "{{BASE_URL}}";
        var apiKey = "{{API_KEY}}";
        
        using var http = new HttpClient();
        http.DefaultRequestHeaders.Add("api-key", apiKey);
        
        // Sends one request and returns the parsed response body. A failed call throws.
        async Task<JsonElement> CallAsync(string method, string path, string? body = null)
        {
            using var request = new HttpRequestMessage(new HttpMethod(method), baseUrl + path);
            if (body is not null)
            {
                request.Content = new StringContent(body, Encoding.UTF8, "application/json");
            }
        
            using var response = await http.SendAsync(request);
            var json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException($"{method} {path} answered {(int)response.StatusCode}: {json}");
            }
        
            return json.Length == 0 ? default : JsonSerializer.Deserialize<JsonElement>(json);
        }
        
        // Turns a value read off a response into the text the API sent. A missing value throws.
        static string Capture(JsonElement value, string name) => value.ValueKind switch
        {
            JsonValueKind.String => value.GetString()!,
            JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.GetRawText(),
            _ => throw new InvalidOperationException($"The response carried no value for {name}.")
        };
        
        // Phase 1: Take the first payment
        
        // Step 1: Charge the card the first time
        var step1 = await CallAsync("POST", "/api/transactions", """
            {
              "transactionType": "Sale",
              "cardData": {
                "cardNumber": "4111111111111111",
                "nameOnCard": "Jane Doe",
                "expirationMonth": 12,
                "expirationYear": 2030,
                "cvv": 123
              },
              "invoiceData": {
                "amounts": { "base": 10.00, "total": 10.00 }
              }
            }
            """);
        var transactionId = Capture(step1.GetProperty("id"), "transactionId");
        var merchantId = Capture(step1.GetProperty("merchantId"), "merchantId");
        
        // Phase 2: Save the card
        
        // Step 2: Turn that sale into a reusable stored payment method
        var step2 = await CallAsync("POST", $"/api/tokens/create-from-transaction-async?transactionId={Uri.EscapeDataString(transactionId)}&merchantId={Uri.EscapeDataString(merchantId)}");
        var savedCardToken = Capture(step2.GetProperty("publicReference"), "savedCardToken");
        
        // Phase 3: Charge it again later
        
        // Step 3: Charge the stored payment method without the card number
        await CallAsync("POST", "/api/transactions", $$"""
            {
              "transactionType": "Sale",
              "initiationType": "CardholderInitiated",
              "tokenData": {
                "token": {{JsonSerializer.Serialize(savedCardToken)}}
              },
              "invoiceData": {
                "amounts": { "base": 9.00, "total": 9.00 }
              }
            }
            """);
        
        // Phase 4: Retire the stored payment method
        
        // Step 4: Look the stored payment method up by its handle
        var step4 = await CallAsync("POST", $"/api/tokens/resolve-payment-token-async?paymentTokenidentifier={Uri.EscapeDataString(savedCardToken)}&transactionMerchantId={Uri.EscapeDataString(merchantId)}&doOwnershipCheck=true");
        var storedTokenId = Capture(step4.GetProperty("id"), "storedTokenId");
        
        // Step 5: Retire the stored payment method
        await CallAsync("POST", $"/api/tokens/deactivate-async?id={Uri.EscapeDataString(storedTokenId)}");
        
        // Step 6: If you need to save a card without charging it
        // This blueprint saves the card it just charged, which is the case an integration that already
        // takes payments grows into. A signup that stores a card before the first order has no sale to mint
        // from, and collecting the number yourself to vault it puts you back in scope for handling it. The
        // hosted payment page has a no-charge save mode for exactly that. The customer enters the card on a
        // page you don't host, and you get the same handle back. The hosted page is also where the
        // customer's stored-credential consent is recorded, which a later charge made without them present
        // has to have. The blueprint below covers the hosted flow end to end.
        

        pip install requests

        Python

        # Save a card and charge it later
        #
        # Take one card sale, keep a reusable handle to the card it was paid with, charge that handle for a
        # second order without the card number, and retire it when the customer is done with you.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        from urllib.parse import quote
        
        import json
        import requests
        
        BASE_URL = "{{BASE_URL}}"
        API_KEY = "{{API_KEY}}"
        
        
        # Sends one request and returns the parsed response body. A failed call raises.
        def call(method, path, body=None):
            response = requests.request(
                method,
                BASE_URL + path,
                headers={"api-key": API_KEY},
                json=body,
            )
            response.raise_for_status()
            return response.json() if response.content else None
        
        
        # Turns a value read off a response into the text the API sent. A missing value raises.
        def capture(value, name):
            if value is None:
                raise ValueError(f"The response carried no value for {name}.")
            return value if isinstance(value, str) else json.dumps(value)
        
        
        # Phase 1: Take the first payment
        
        # Step 1: Charge the card the first time
        step1 = call("POST", "/api/transactions", {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        })
        transaction_id = capture(step1["id"], "transactionId")
        merchant_id = capture(step1["merchantId"], "merchantId")
        
        # Phase 2: Save the card
        
        # Step 2: Turn that sale into a reusable stored payment method
        step2 = call("POST", f"/api/tokens/create-from-transaction-async?transactionId={quote(transaction_id, safe='')}&merchantId={quote(merchant_id, safe='')}")
        saved_card_token = capture(step2["publicReference"], "savedCardToken")
        
        # Phase 3: Charge it again later
        
        # Step 3: Charge the stored payment method without the card number
        call("POST", "/api/transactions", {
          "transactionType": "Sale",
          "initiationType": "CardholderInitiated",
          "tokenData": {
            "token": saved_card_token
          },
          "invoiceData": {
            "amounts": { "base": 9.00, "total": 9.00 }
          }
        })
        
        # Phase 4: Retire the stored payment method
        
        # Step 4: Look the stored payment method up by its handle
        step4 = call("POST", f"/api/tokens/resolve-payment-token-async?paymentTokenidentifier={quote(saved_card_token, safe='')}&transactionMerchantId={quote(merchant_id, safe='')}&doOwnershipCheck=true")
        stored_token_id = capture(step4["id"], "storedTokenId")
        
        # Step 5: Retire the stored payment method
        call("POST", f"/api/tokens/deactivate-async?id={quote(stored_token_id, safe='')}")
        
        # Step 6: If you need to save a card without charging it
        # This blueprint saves the card it just charged, which is the case an integration that already takes
        # payments grows into. A signup that stores a card before the first order has no sale to mint from,
        # and collecting the number yourself to vault it puts you back in scope for handling it. The hosted
        # payment page has a no-charge save mode for exactly that. The customer enters the card on a page
        # you don't host, and you get the same handle back. The hosted page is also where the customer's
        # stored-credential consent is recorded, which a later charge made without them present has to have.
        # The blueprint below covers the hosted flow end to end.
        

        Reconnecting to the server

        Could not reconnect

        This session has ended

        Attempt 1

        Your work on this page is still here. Retrying keeps it; reloading starts the page again.

        The server no longer holds this page's state, so it has to be loaded again.