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
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:
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"
}
}'
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-creationaccount setting. See Request Account Setting Access. - A restricted API key must have the
payment-method:writepermission in addition topayment:write. Secret keys have all permissions.
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_detailsaccepts the same fields as Create Bank Account.- Include
account_holder_nameandaccount_holder_type. Some processors require them. - You cannot combine
bank_account_detailswithbank_account,card,apple_pay, orgoogle_pay. - If you omit the top-level
customerorbilling_details, the payment uses thecustomer_idandbilling_detailsinsidebank_account_details. - The response returns the saved bank account in
payment_method.bank_account. Use itsidasbank_accountfor later payments. - Payment intents accept
bank_account_detailswhen you create the intent. An update request withbank_account_detailsreturns 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.
{
"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:
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:
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:
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:
{
"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: