Skip to main content

Accept Bank Account Payments

This guide processes an ACH payment from your server with a saved bank account or with bank account details. Save the bank account first with Save Bank Accounts.

Before you start​

Credova account
Sign up or sign in to the Credova Portal.
Secret Key
Authenticates requests from your server. Never send it to the browser.

Process the Payment​

Use the bank account you saved by manual entry or instant verification to create a new payment.

Use the id of the collected bank account, such as ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ, as the bank_account value in the payment.

Create a Payment​

We need to make a call to Create Payment endpoint:

Create a Payment
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"bank_account": "ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'
The amount is provided in cents. 1000 is the equivalent of $10.00
The IDEMPOTENCY-KEY header can be passed to protect against duplicate payments being processed.

Create with Bank Account Details​

If you have not saved the bank account yet, you can send its details with the payment. The API saves the bank account and charges it in the same request, so you do not need to call Create Bank Account first.

Before you use bank_account_details:

  • Your account must have the payment-methods:bank-accounts:manual-creation account setting. See Request Account Setting Access.
  • A restricted API key must have the payment-method:write permission in addition to payment:write. Secret keys have all permissions.
Create a Payment with Bank Account Details
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"bank_account_details": {
"account_holder_name": "John Doe",
"account_holder_type": "individual",
"account_type": "checking",
"routing_number": "110000000",
"account_number": "000123456789"
}
},
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'
  • bank_account_details accepts the same fields as Create Bank Account.
  • Include account_holder_name and account_holder_type. Some processors require them.
  • You cannot combine bank_account_details with bank_account, card, apple_pay, or google_pay.
  • If you omit the top-level customer or billing_details, the payment uses the customer_id and billing_details inside bank_account_details.
  • The response returns the saved bank account in payment_method.bank_account. Use its id as bank_account for later payments.
  • Payment intents accept bank_account_details when you create the intent. An update request with bank_account_details returns a 400.

Send an IDEMPOTENCY-KEY with each request. If a request fails and you retry it with the same key, the payment uses the bank account that the first request saved. A retry with the same key and different bank account details returns a 400. Use a new key when you change the bank account details.

Retry with different bank account details
{
"title": "Error",
"status": 400,
"detail": "'bank_account_details' differs from the request first sent with this IDEMPOTENCY-KEY."
}

Create with Existing Customer​

If you created a customer, then you can pass its ID as customer.id when processing the payment:

Create a Payment with Customer
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"bank_account": "ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'

Create with Customer's Payment Method​

If you attached the bank account to a customer, then you can omit the customer section when processing the payment:

Create a Payment with Payment Method
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"bank_account": "ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'

Create with Payment Method's Billing Details​

If you supplied the billing_details or updated the bank account, then you can omit the billing_details section when processing the payment:

Create a Payment with Payment Method
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"bank_account": "ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
}
}'

Payment Response​

Given the above examples, you should see a payment result similar to:

{
"id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "pending",
"transaction_id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"amount": 1000,
"amount_capturable": 0,
"amount_charged": 1000,
"amount_refunded": 0,
"refunded": false,
"currency": "USD",
"payment_method": {
"bank_account": {
"id": "ba_7Ay5mcUXAxwrN6wQEQUVEHBCJ",
"account_holder_name": "John Doe",
"account_holder_type": "individual",
"account_type": "individual",
"routing_number": "123456789",
"account_number_last4": "1011"
}
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"shipping_address": {
"address_line_1": "111 Colorado Ave.",
"address_line_2": "Apt 403",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"fraud_decision": {
"decision": "accept"
},
"transaction": {
"id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "pending",
"amount": 1000,
"fee_amount": 0,
"net_amount": 1000,
"currency": "USD",
"type_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"type": "payment",
"processor": "nuvei",
"processor_id": "7110000000010554769",
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
},
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
}

Payment Status​

ACH payments do not settle instantly. A new ACH payment has the pending status. When the bank transfer settles, the payment moves to succeeded. Subscribe to the payment:update webhook to receive each status change.

If the bank rejects the debit, the payment moves to declined. The declined_reason field contains the ACH return code and its description:

Declined ACH payment
{
"id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"status": "declined",
"declined_reason": "R02 - Account Closed"
}

Some return codes also change the bank account's status to closed or invalid. A new payment with that bank account returns a 400 with bank account has 'closed' status. See Bank Accounts, and collect a new bank account from the customer.

You can refund an ACH payment after it reaches succeeded.

Conclusion​

You processed an ACH payment. To manage the payment or view its details, follow these guides: