# Simulate an ACH return

Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the response.

2 steps, 1 API call

**Products:** ACH, 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.

## Reproduce it in the sandbox

### 1. Send the ACH sale at the returning amount

API call

`POST /api/transactions`

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

A transaction carrying check data runs on the ACH rail, where the cents of the amount select the return. The account and routing numbers below are test values that reach nothing.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "checkData": {
      "nameOnCheck": "Jane Doe",
      "routingNumber": "021000021",
      "accountNumber": "1234567890",
      "accountType": "Checking",
      "secCode": "Ppd"
    },
    "invoiceData": {
      "amounts": { "base": 25.01, "total": 25.01 }
    }
  }'
```

.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",
    checkData = new
    {
        nameOnCheck = "Jane Doe",
        routingNumber = "021000021",
        accountNumber = "1234567890",
        accountType = "Checking",
        secCode = "Ppd"
    },
    invoiceData = new
    {
        amounts = new { @base = 25.01m, total = 25.01m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var returnCode = result.GetProperty("responseData").GetProperty("nachaReturnCode").GetString();
```

### 2. Read the outcome off the response

On your side

The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the response body. The platform files that result under the "Declined" outcome. The response carries the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the debit was accepted, so your integration has to be able to leave and re-enter an accepted-then-returned state. The sandbox collapses that wait to a single call. What this particular call doesn't produce is a notification: the sale was refused, so no settlement status ever moves. The ordinary decline notification fires; the ACH status-changed and returned events don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return, send a sale at an approving amount and move it with POST /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a settle and then a return on one transaction and the return is flagged late, which is the case worth proving.

[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": "Decline",
  "responseData": {
    "resultCode": "Decline",
    "resultMessage": "ACH Returned (R01)",
    "nachaReturnCode": "R01",
    "nachaReturnReason": "Insufficient Funds"
  }
}
```

## 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
# Simulate an ACH return
#
# Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the
# response.
#
# 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"
}

# Phase 1: Reproduce it in the sandbox

# Step 1: Send the ACH sale at the returning amount
call POST "/api/transactions" '{
  "transactionType": "Sale",
  "checkData": {
    "nameOnCheck": "Jane Doe",
    "routingNumber": "021000021",
    "accountNumber": "1234567890",
    "accountType": "Checking",
    "secCode": "Ppd"
  },
  "invoiceData": {
    "amounts": { "base": 25.01, "total": 25.01 }
  }
}' > /dev/null

# Step 2: Read the outcome off the response
# The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the
# response body. The platform files that result under the "Declined" outcome. The response carries
# the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the
# debit was accepted, so your integration has to be able to leave and re-enter an
# accepted-then-returned state. The sandbox collapses that wait to a single call. What this
# particular call doesn't produce is a notification: the sale was refused, so no settlement status
# ever moves. The ordinary decline notification fires; the ACH status-changed and returned events
# don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return,
# send a sale at an approving amount and move it with POST
# /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a
# settle and then a return on one transaction and the return is flagged late, which is the case
# worth proving.
```

### PowerShell

```powershell
# Simulate an ACH return
#
# Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the
# response.
#
# 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
}

# Phase 1: Reproduce it in the sandbox

# Step 1: Send the ACH sale at the returning amount
$body = @'
{
  "transactionType": "Sale",
  "checkData": {
    "nameOnCheck": "Jane Doe",
    "routingNumber": "021000021",
    "accountNumber": "1234567890",
    "accountType": "Checking",
    "secCode": "Ppd"
  },
  "invoiceData": {
    "amounts": { "base": 25.01, "total": 25.01 }
  }
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Step 2: Read the outcome off the response
# The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the
# response body. The platform files that result under the "Declined" outcome. The response carries
# the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the
# debit was accepted, so your integration has to be able to leave and re-enter an
# accepted-then-returned state. The sandbox collapses that wait to a single call. What this
# particular call doesn't produce is a notification: the sale was refused, so no settlement status
# ever moves. The ordinary decline notification fires; the ACH status-changed and returned events
# don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return,
# send a sale at an approving amount and move it with POST
# /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a
# settle and then a return on one transaction and the return is flagged late, which is the case
# worth proving.
```

### TypeScript

```typescript
// Simulate an ACH return
//
// Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the
// response.
//
// 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;
}

// Phase 1: Reproduce it in the sandbox

// Step 1: Send the ACH sale at the returning amount
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "checkData": {
    "nameOnCheck": "Jane Doe",
    "routingNumber": "021000021",
    "accountNumber": "1234567890",
    "accountType": "Checking",
    "secCode": "Ppd"
  },
  "invoiceData": {
    "amounts": { "base": 25.01, "total": 25.01 }
  }
});

// Step 2: Read the outcome off the response
// The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the
// response body. The platform files that result under the "Declined" outcome. The response carries
// the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the
// debit was accepted, so your integration has to be able to leave and re-enter an
// accepted-then-returned state. The sandbox collapses that wait to a single call. What this
// particular call doesn't produce is a notification: the sale was refused, so no settlement status
// ever moves. The ordinary decline notification fires; the ACH status-changed and returned events
// don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return,
// send a sale at an approving amount and move it with POST
// /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a
// settle and then a return on one transaction and the return is flagged late, which is the case
// worth proving.
```

### C#

```csharp
// Simulate an ACH return
//
// Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the
// response.
//
// 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);
}

// Phase 1: Reproduce it in the sandbox

// Step 1: Send the ACH sale at the returning amount
await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "checkData": {
        "nameOnCheck": "Jane Doe",
        "routingNumber": "021000021",
        "accountNumber": "1234567890",
        "accountType": "Checking",
        "secCode": "Ppd"
      },
      "invoiceData": {
        "amounts": { "base": 25.01, "total": 25.01 }
      }
    }
    """);

// Step 2: Read the outcome off the response
// The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the
// response body. The platform files that result under the "Declined" outcome. The response carries
// the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the
// debit was accepted, so your integration has to be able to leave and re-enter an
// accepted-then-returned state. The sandbox collapses that wait to a single call. What this
// particular call doesn't produce is a notification: the sale was refused, so no settlement status
// ever moves. The ordinary decline notification fires; the ACH status-changed and returned events
// don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return,
// send a sale at an approving amount and move it with POST
// /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a
// settle and then a return on one transaction and the return is flagged late, which is the case
// worth proving.
```

### Python

```bash
pip install requests
```

```python
# Simulate an ACH return
#
# Send an ACH sale at an amount the sandbox returns, and read the NACHA return code off the
# response.
#
# 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}}"


# 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: Reproduce it in the sandbox

# Step 1: Send the ACH sale at the returning amount
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "checkData": {
    "nameOnCheck": "Jane Doe",
    "routingNumber": "021000021",
    "accountNumber": "1234567890",
    "accountType": "Checking",
    "secCode": "Ppd"
  },
  "invoiceData": {
    "amounts": { "base": 25.01, "total": 25.01 }
  }
})

# Step 2: Read the outcome off the response
# The sandbox answers with the result code "Decline" and the message "ACH Returned (R01)" in the
# response body. The platform files that result under the "Declined" outcome. The response carries
# the NACHA return code R01 (Insufficient Funds). On the live rail a return arrives days after the
# debit was accepted, so your integration has to be able to leave and re-enter an
# accepted-then-returned state. The sandbox collapses that wait to a single call. What this
# particular call doesn't produce is a notification: the sale was refused, so no settlement status
# ever moves. The ordinary decline notification fires; the ACH status-changed and returned events
# don't. Read the NACHA code off the response here. Then, to prove your endpoint handles a return,
# send a sale at an approving amount and move it with POST
# /api/transactions/{id}/sandbox/ach-status, which publishes both ACH events on demand. Chain a
# settle and then a return on one transaction and the return is flagged late, which is the case
# worth proving.
```

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