# Exercise processor failover

Force a processor to report that it didn't process a transaction, and see both of what happens next: a fallback processor approving, and failover running out of processors.

5 steps, 2 API calls

**Products:** Payments, Transactions

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.

## Give the merchant somewhere to fail over to

### 1. Configure a second processor profile and note the primary's id

On your side

The primary profile reports that it did not process, authorization failover selects the fallback profile, and the fallback approves. Requires the merchant to have a second active processor profile. Add a second active loopback processor profile to your sandbox merchant, then copy the id of the profile that authorizes today. That id is what the first run below sends, and it's the only value on this page you supply yourself. A merchant with one profile can still run the second case, and it's refused the same way, but the error code is "ProcessorNotConfigured" because there's no second processor to try.

**Values this step gives you**

- `{{primaryProfileId}}`: The id of the processor profile that should report it didn't process. Read it off the merchant's processor profiles in the application.

## Run both failover outcomes

### 2. Make the primary profile refuse to process

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

A control field provokes this run, riding on a transaction custom field. Send "loopback.notProcessedProfileId" or "loopbacknotprocessedprofileid" and the simulator reads them identically. A comma-separated list of processor profile ids. Only the listed profiles report that they did not process, so the primary can refuse while the fallback approves. The amount is the sandbox's guaranteed approval, so whatever comes back is failover's doing and not the amount's. Naming a profile is what separates this from a plain decline. The named profile reports that it didn't process the transaction at all, which is a different thing from refusing it. A decline is an answer and routing accepts it. Failing to process leaves the transaction unanswered and sends routing looking for another processor.

cURL:

```bash
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
    },
    "customFields": [
      { "name": "loopback.notProcessedProfileId", "value": "{{primaryProfileId}}" }
    ],
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
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
    },
    customFields = new[]
    {
        new { name = "loopback.notProcessedProfileId", value = "{{primaryProfileId}}" }
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();

if (response.IsSuccessStatusCode)
{
    // A transaction: approved on whichever profile answered.
    var resultCode = result.GetProperty("responseData").GetProperty("resultCode").GetString();
}
else
{
    // No transaction: no processor took the payment, and it's safe to retry.
    var errorCode = result.GetProperty("error").GetProperty("code").GetString();
}
```

### 3. Confirm the fallback answered

On your side

The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The message reads "Approved". Nothing on the response says which profile answered, so this is worth confirming in the application rather than in your own code: the transaction's processor profile is the fallback, not the one you named. What your integration sees is an ordinary approval, which is the point. Failover is meant to be invisible to the caller.

[Testing your integration](https://devportal-simpay-sbx.winkpg.io/docs/testing.md)

**What this step answers with** (HTTP 200)

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

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

### 4. Make every profile refuse to process

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

A control field provokes this run, riding on a transaction custom field. Send "loopback.notProcessed" or "loopbacknotprocessed" and the simulator reads them identically. Set to true and every loopback profile reports that it did not process the transaction, so authorization failover is attempted and then exhausted. The amount is the sandbox's guaranteed approval, so whatever comes back is failover's doing and not the amount's. The same run with nothing left to fail over to. This is the case worth writing code for, and the one almost nobody can reproduce on a live processor.

cURL:

```bash
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
    },
    "customFields": [
      { "name": "loopback.notProcessed", "value": "true" }
    ],
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
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
    },
    customFields = new[]
    {
        new { name = "loopback.notProcessed", value = "true" }
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();

if (response.IsSuccessStatusCode)
{
    // A transaction: approved on whichever profile answered.
    var resultCode = result.GetProperty("responseData").GetProperty("resultCode").GetString();
}
else
{
    // No transaction: no processor took the payment, and it's safe to retry.
    var errorCode = result.GetProperty("error").GetProperty("code").GetString();
}
```

### 5. Handle the exhausted case

On your side

The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with a transaction. The sandbox files the code under the "Declined" outcome. The message reads "The transaction could not be processed by an available processor." This isn't a refusal by an issuer, even where the code matches one. An issuer's decline answers 200 with the transaction; this answers with an error and no transaction, because no processor ever took the payment. Treating it as a decline tells the payer their card was declined when their card was never asked. It's safe to retry, unlike a decline, so an integration that branches on the status recovers from a processor outage without sending the payer away.

[Testing your integration](https://devportal-simpay-sbx.winkpg.io/docs/testing.md)

**What this step answers with** (HTTP 409)

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

```json
{
  "error": {
    "code": "Decline",
    "message": "The transaction could not be processed by an available processor."
  }
}
```

## 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.

### cURL

```bash
brew install jq
```

```bash
#!/usr/bin/env bash
# Exercise processor failover
#
# Force a processor to report that it didn't process a transaction, and see both of what happens
# next: a fallback processor approving, and failover running out of processors.
#
# 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}}"

# Values you supply. Set each one before you run the script.
# The id of the processor profile that should report it didn't process. Read it off the merchant's
# processor profiles in the application.
primaryProfileId=""

# 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"
}

# Phase 1: Give the merchant somewhere to fail over to

# Step 1: Configure a second processor profile and note the primary's id
# The primary profile reports that it did not process, authorization failover selects the fallback
# profile, and the fallback approves. Requires the merchant to have a second active processor
# profile. Add a second active loopback processor profile to your sandbox merchant, then copy the id
# of the profile that authorizes today. That id is what the first run below sends, and it's the only
# value on this page you supply yourself. A merchant with one profile can still run the second case,
# and it's refused the same way, but the error code is "ProcessorNotConfigured" because there's no
# second processor to try.

# Phase 2: Run both failover outcomes

# Step 2: Make the primary profile refuse to process
body=$(jq -n --arg primaryProfileId "$primaryProfileId" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessedProfileId", "value": $primaryProfileId }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}')
call POST "/api/transactions" "$body" > /dev/null

# Step 3: Confirm the fallback answered
# The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The
# message reads "Approved". Nothing on the response says which profile answered, so this is worth
# confirming in the application rather than in your own code: the transaction's processor profile is
# the fallback, not the one you named. What your integration sees is an ordinary approval, which is
# the point. Failover is meant to be invisible to the caller.

# Step 4: Make every profile refuse to process
call POST "/api/transactions" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessed", "value": "true" }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}' > /dev/null

# Step 5: Handle the exhausted case
# The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with a
# transaction. The sandbox files the code under the "Declined" outcome. The message reads "The
# transaction could not be processed by an available processor." This isn't a refusal by an issuer,
# even where the code matches one. An issuer's decline answers 200 with the transaction; this
# answers with an error and no transaction, because no processor ever took the payment. Treating it
# as a decline tells the payer their card was declined when their card was never asked. It's safe to
# retry, unlike a decline, so an integration that branches on the status recovers from a processor
# outage without sending the payer away.
```

### PowerShell

```powershell
# Exercise processor failover
#
# Force a processor to report that it didn't process a transaction, and see both of what happens
# next: a fallback processor approving, and failover running out of processors.
#
# 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}}'

# Values you supply. Set each one before you run the script.
# The id of the processor profile that should report it didn't process. Read it off the merchant's
# processor profiles in the application.
$primaryProfileId = ''

# 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
}

# Phase 1: Give the merchant somewhere to fail over to

# Step 1: Configure a second processor profile and note the primary's id
# The primary profile reports that it did not process, authorization failover selects the fallback
# profile, and the fallback approves. Requires the merchant to have a second active processor
# profile. Add a second active loopback processor profile to your sandbox merchant, then copy the id
# of the profile that authorizes today. That id is what the first run below sends, and it's the only
# value on this page you supply yourself. A merchant with one profile can still run the second case,
# and it's refused the same way, but the error code is "ProcessorNotConfigured" because there's no
# second processor to try.

# Phase 2: Run both failover outcomes

# Step 2: Make the primary profile refuse to process
$body = @"
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessedProfileId", "value": $(ConvertTo-Json -InputObject ([string]($primaryProfileId))) }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
"@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Step 3: Confirm the fallback answered
# The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The
# message reads "Approved". Nothing on the response says which profile answered, so this is worth
# confirming in the application rather than in your own code: the transaction's processor profile is
# the fallback, not the one you named. What your integration sees is an ordinary approval, which is
# the point. Failover is meant to be invisible to the caller.

# Step 4: Make every profile refuse to process
$body = @'
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessed", "value": "true" }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Step 5: Handle the exhausted case
# The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with a
# transaction. The sandbox files the code under the "Declined" outcome. The message reads "The
# transaction could not be processed by an available processor." This isn't a refusal by an issuer,
# even where the code matches one. An issuer's decline answers 200 with the transaction; this
# answers with an error and no transaction, because no processor ever took the payment. Treating it
# as a decline tells the payer their card was declined when their card was never asked. It's safe to
# retry, unlike a decline, so an integration that branches on the status recovers from a processor
# outage without sending the payer away.
```

### TypeScript

```typescript
// Exercise processor failover
//
// Force a processor to report that it didn't process a transaction, and see both of what happens
// next: a fallback processor approving, and failover running out of processors.
//
// 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}}';

// Values you supply. Set each one before you run the script.
// The id of the processor profile that should report it didn't process. Read it off the merchant's
// processor profiles in the application.
const primaryProfileId = '';

// 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;
}

// Phase 1: Give the merchant somewhere to fail over to

// Step 1: Configure a second processor profile and note the primary's id
// The primary profile reports that it did not process, authorization failover selects the fallback
// profile, and the fallback approves. Requires the merchant to have a second active processor
// profile. Add a second active loopback processor profile to your sandbox merchant, then copy the
// id of the profile that authorizes today. That id is what the first run below sends, and it's the
// only value on this page you supply yourself. A merchant with one profile can still run the second
// case, and it's refused the same way, but the error code is "ProcessorNotConfigured" because
// there's no second processor to try.

// Phase 2: Run both failover outcomes

// Step 2: Make the primary profile refuse to process
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessedProfileId", "value": primaryProfileId }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});

// Step 3: Confirm the fallback answered
// The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The
// message reads "Approved". Nothing on the response says which profile answered, so this is worth
// confirming in the application rather than in your own code: the transaction's processor profile
// is the fallback, not the one you named. What your integration sees is an ordinary approval, which
// is the point. Failover is meant to be invisible to the caller.

// Step 4: Make every profile refuse to process
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessed", "value": "true" }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});

// Step 5: Handle the exhausted case
// The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with
// a transaction. The sandbox files the code under the "Declined" outcome. The message reads "The
// transaction could not be processed by an available processor." This isn't a refusal by an issuer,
// even where the code matches one. An issuer's decline answers 200 with the transaction; this
// answers with an error and no transaction, because no processor ever took the payment. Treating it
// as a decline tells the payer their card was declined when their card was never asked. It's safe
// to retry, unlike a decline, so an integration that branches on the status recovers from a
// processor outage without sending the payer away.
```

### C#

```csharp
// Exercise processor failover
//
// Force a processor to report that it didn't process a transaction, and see both of what happens
// next: a fallback processor approving, and failover running out of processors.
//
// 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}}";

// Values you supply. Set each one before you run the script.
// The id of the processor profile that should report it didn't process. Read it off the merchant's
// processor profiles in the application.
var primaryProfileId = "";

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);
}

// Phase 1: Give the merchant somewhere to fail over to

// Step 1: Configure a second processor profile and note the primary's id
// The primary profile reports that it did not process, authorization failover selects the fallback
// profile, and the fallback approves. Requires the merchant to have a second active processor
// profile. Add a second active loopback processor profile to your sandbox merchant, then copy the
// id of the profile that authorizes today. That id is what the first run below sends, and it's the
// only value on this page you supply yourself. A merchant with one profile can still run the second
// case, and it's refused the same way, but the error code is "ProcessorNotConfigured" because
// there's no second processor to try.

// Phase 2: Run both failover outcomes

// Step 2: Make the primary profile refuse to process
await CallAsync("POST", "/api/transactions", $$"""
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "cvv": 123
      },
      "customFields": [
        { "name": "loopback.notProcessedProfileId", "value": {{JsonSerializer.Serialize(primaryProfileId)}} }
      ],
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);

// Step 3: Confirm the fallback answered
// The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The
// message reads "Approved". Nothing on the response says which profile answered, so this is worth
// confirming in the application rather than in your own code: the transaction's processor profile
// is the fallback, not the one you named. What your integration sees is an ordinary approval, which
// is the point. Failover is meant to be invisible to the caller.

// Step 4: Make every profile refuse to process
await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "cvv": 123
      },
      "customFields": [
        { "name": "loopback.notProcessed", "value": "true" }
      ],
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);

// Step 5: Handle the exhausted case
// The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with
// a transaction. The sandbox files the code under the "Declined" outcome. The message reads "The
// transaction could not be processed by an available processor." This isn't a refusal by an issuer,
// even where the code matches one. An issuer's decline answers 200 with the transaction; this
// answers with an error and no transaction, because no processor ever took the payment. Treating it
// as a decline tells the payer their card was declined when their card was never asked. It's safe
// to retry, unlike a decline, so an integration that branches on the status recovers from a
// processor outage without sending the payer away.
```

### Python

```bash
pip install requests
```

```python
# Exercise processor failover
#
# Force a processor to report that it didn't process a transaction, and see both of what happens
# next: a fallback processor approving, and failover running out of processors.
#
# 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.

import requests

BASE_URL = "{{BASE_URL}}"
API_KEY = "{{API_KEY}}"

# Values you supply. Set each one before you run the script.
# The id of the processor profile that should report it didn't process. Read it off the merchant's
# processor profiles in the application.
primary_profile_id = ""


# 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


# Phase 1: Give the merchant somewhere to fail over to

# Step 1: Configure a second processor profile and note the primary's id
# The primary profile reports that it did not process, authorization failover selects the fallback
# profile, and the fallback approves. Requires the merchant to have a second active processor
# profile. Add a second active loopback processor profile to your sandbox merchant, then copy the id
# of the profile that authorizes today. That id is what the first run below sends, and it's the only
# value on this page you supply yourself. A merchant with one profile can still run the second case,
# and it's refused the same way, but the error code is "ProcessorNotConfigured" because there's no
# second processor to try.

# Phase 2: Run both failover outcomes

# Step 2: Make the primary profile refuse to process
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessedProfileId", "value": primary_profile_id }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})

# Step 3: Confirm the fallback answered
# The sandbox answers with the result code "Ok" and files it under the "Approved" outcome. The
# message reads "Approved". Nothing on the response says which profile answered, so this is worth
# confirming in the application rather than in your own code: the transaction's processor profile is
# the fallback, not the one you named. What your integration sees is an ordinary approval, which is
# the point. Failover is meant to be invisible to the caller.

# Step 4: Make every profile refuse to process
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "cvv": 123
  },
  "customFields": [
    { "name": "loopback.notProcessed", "value": "true" }
  ],
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})

# Step 5: Handle the exhausted case
# The API refuses the request with HTTP 409 and the error code "Decline" instead of answering with a
# transaction. The sandbox files the code under the "Declined" outcome. The message reads "The
# transaction could not be processed by an available processor." This isn't a refusal by an issuer,
# even where the code matches one. An issuer's decline answers 200 with the transaction; this
# answers with an error and no transaction, because no processor ever took the payment. Treating it
# as a decline tells the payer their card was declined when their card was never asked. It's safe to
# retry, unlike a decline, so an integration that branches on the status recovers from a processor
# outage without sending the payer away.
```

- [Blueprints](https://devportal-simpay-sbx.winkpg.io/docs/blueprints.md): every blueprint this instance publishes.

## See also

- [All documentation](https://devportal-simpay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
