ACH Payments

This shows you how to accept US and South African ACH charges from your customers.

Rave allows you to charge US and SA accounts using the ACH account charge flow. Your customers would be taken to a page where they can select their bank and login into their Internet banking to complete the transaction.

What you need to know before you start accepting ACH payments.

  1. Sign up for an account here .

  2. You can use webhooks to get notified on transactions using the Webhooks Guide

  3. Pass a redirect_url so we can redirect your customer back to the page you want them to view after completing a charge. We append the response of the transaction as query parameters to your redirect url .

Step 1: Collect the customer's details.

You can use a custom form to collect the customer's details like name, email and phone number including extra information you might need and pass it as meta. See a sample of the payment object below.

{
  "PBFPubKey": "FLWPUBK-7adb6177bd71dd43c2efa3f1229e3b7f-X",
  "currency": "USD",
  "payment_type": "account",
  "country": "US",
  "amount": "20",
  "email": "[email protected]",
  "phonenumber": "0000000000",
  "firstname": "Temi",
  "lastname": "Tester",
  "IP": "355426087298442",
  "txRef": "rave-checkout-" + Date.now(),
  "is_us_bank_charge": "true",
  "redirect_url": "https://rave-webhook.herokuapp.com/receivepayment",
  "device_fingerprint": "69e6b7f0b72037aa8428b70fbe03986c"
}
{
  "PBFPubKey": "FLWPUBK-7adb6177bd71dd43c2efa3f1229e3b7f-X",
  "accountnumber": "0000000000",
  "accountbank": "000",
  "currency": "ZAR",
  "payment_type": "account",
  "country": "ZA",
  "amount": "20",
  "email": "[email protected]",
  "phonenumber": "0000000000",
  "firstname": "Temi",
  "lastname": "Tester",
  "IP": "355426087298442",
  "txRef": "rave-checkout-" + Date.now(),
  "redirect_url": "https://rave-webhook.herokuapp.com/receivepayment",
  "device_fingerprint": "69e6b7f0b72037aa8428b70fbe03986c"
}
ParametersRequiredDescription
PBFPubkeyTrueThis is a unique key generated for each button created on Rave’s dashboard. It starts with a prefix FLWPUBK and ends with suffix X.
currencyfalse
defaults to NGN
Pass currency as USD for US ACH payment and ZAR for SA ACH payment.
payment_typeTrue (Expected value: account)Pass your payment type as account to the API.
countryFalse (defaults to NG)Pass your country as US for US ACH payments and ZA for SA ACH payments.
amountTrueThis is the amount to be charged from the account it is passed as a whole number e.g. "amount": 100 is 100 USD or 100 ZAR
emailTrueThis is the email address of the customer.
phonenumberFalseThis is the phone number of the customer.
firstnameFalseThis is the first name of the card holder or the customer.
lastnameFalseThis is the last name of the card holder or the customer.
IPFalseIP - Internet Protocol. This represents the current IP address of the customer carrying out the transaction.
txRefTrueThis is the unique reference, unique to the particular transaction being carried out. It is generated by the merchant for every transaction
metaFalseSet of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
subaccountsFalseThis is an array of objects containing the subaccount IDs to split the payment into. This is only required when initiating a Split Payment transaction.
redirect_urlTrueThis is a url you provide, we redirect to it after the customer completes payment and append the response to it as query parameters.
device_fingerprintFalseThis is the fingerpringt for the device being used. It can be generated using a library on whatever platform is being used.
is_us_bank_chargeTruePass this when making a US ACH charge. The expected value is 1

Step 2: Encrypt your request

To see how to encrypt using any of our encryption functions copy visit the Rave Encryption section.

Step 3: Initiate your payment

After encrypting, the next step is to initiate your payment using the encrypted string by sending a request to the /charge endpoint. See how to do that below.

API Endpoint: https://api.ravepay.co/flwv3-pug/getpaidx/api/charge

Sample Request:

{
  "PBFPubKey": "FLWPUBK-7adb6177bd71dd43c2efa3f1229e3b7f-X",
  "client": "VodhvFFsni0CBeieHPq9HTuG5lbNPgmD5rbEw6Uxb0TD9eD9B3VM5uZ1B5lC3thQMbPypNBCAYykQt74g4x4l0DDFmuEwyIsOCSsOU6MqqIJd5mqxHVpFRu+OaSOzrm9nMXbTwwxnJ/Ay8R4CSOcp/eceUXRommw5FaPdU8/jLzbuCeIYBtcqR738mQfK3f/ieeXzZY8MYgf+nOBmJZEXZF9bKCExsjKn84jeK055gp56ZpCk0hFCa9f3NMxUQw5MzO3zY7qArj2XvgMQONcum50KqHoOeylPeUNE3gre3BKV3ZVNpEmdd4wTLF57CWR7EjGog/Hu3RcEnm5CAGrvfJSJ9UlNcuAIJj9yH3+SHK4XRZSXxYegdc/0C0z9BmopDjMMXbKBT8gSw3Ux0Lx0ajHKk4BX7COUQbJwspTqd+B+A+qRkH6QhUFz08H7tOQ+4qq/mP3SVOhEZn9Hs0mc8nw2FiYfbyM965oGp/dR843Ezo4lOYBzixZmsHtu7bKMT8qFEGbQ1rmk8cJgjU5trdNsR7RRWrkQIZ4zxEPXEdBXpW/1DeaROS8l3qLdlb+eHu7gVS05LE=",
  "alg": "3DES-24"
}
  • client: This is the encrypted request parameters.

  • PBFPubKey: This is your merchant public key.

  • alg: must always be passed as 3DES-24

When you initiate the payment you would get a response that looks like the response below.

{
  "status": "success",
  "message": "V-COMP",
  "data": {
    "id": 174024,
    "txRef": "rave-checkout-1529594387128",
    "orderRef": null,
    "flwRef": "FLWT000985573",
    "redirectUrl": "https://rave-webhook.herokuapp.com/receivepayment",
    "device_fingerprint": "69e6b7f0b72037aa8428b70fbe03986c",
    "settlement_token": null,
    "cycle": "one-time",
    "amount": 20,
    "charged_amount": 20,
    "appfee": 0,
    "merchantfee": 0,
    "merchantbearsfee": 1,
    "chargeResponseCode": "02",
    "raveRef": null,
    "chargeResponseMessage": "Pending Validation",
    "authModelUsed": "AUTH",
    "currency": "USD",
    "IP": "::ffff:10.65.204.22",
    "narration": "Synergy Group",
    "status": "success-pending-validation",
    "modalauditid": "86b720efbd4cabe890def64945fe757a",
    "vbvrespmessage": "N/A",
    "authurl": "https://flutterwavestaging.com:9443/flwusprocessor/redirect?hid=FLW3f9f99f0e5534d438c15297bc608f21d",
    "vbvrespcode": "N/A",
    "acctvalrespmsg": null,
    "acctvalrespcode": null,
    "paymentType": "account-ach-us",
    "paymentPlan": null,
    "paymentPage": null,
    "paymentId": "N/A",
    "fraud_status": "ok",
    "charge_type": "normal",
    "is_live": 0,
    "createdAt": "2018-06-21T15:23:12.000Z",
    "updatedAt": "2018-06-21T15:23:16.000Z",
    "deletedAt": null,
    "customerId": 22892,
    "AccountId": 134,
    "customer": {
      "id": 22892,
      "phone": "0000000000",
      "fullName": "Temi Tester",
      "customertoken": null,
      "email": "[email protected]",
      "createdAt": "2018-04-09T01:07:13.000Z",
      "updatedAt": "2018-04-09T01:07:13.000Z",
      "deletedAt": null,
      "AccountId": 134
    },
    "validateInstructions": {
      "valparams": [],
      "instruction": ""
    }
  }
}

Some of the important responses you need to check are broken down below:

  • data.chargeResponseCode: This is the response code of the transaction, it typically tells you when a transaction is successful with a response code 00 or when the transaction requires validation 02.

  • data.chargeResponseMessage: This is the response message and it can be shown to the customer to show what needs to be done next.

  • data.authModelUsed: This shows you the authentication model used for the transaction, it can also help you decide internally what steps to take after the payment is initiated, e.g. if the value is PIN the customer would be required to submit their otp based on the message returned in chargeResponseMessage or if the value is VBVSECURECODE you would be required to load the authurl returned in the response in an iframe.

  • data.authurl: This is used for authenticating the customer in a VBVSECURECODE transaction, you need to load it in an iFrame if returned to you

  • data.paymentType: This shows you the payment instrument used i.e. if the customer used a card, account or ussd to complete the payment.

📘

When performing an ACH charge, the payment validation happens on the banks' page. Once the transaction is completed, we redirect the customer to the redirect_url you sent in the initial charge request and append a response. We also send you a Webhook notification so you can process the payment.

Page customer is redirected to complete payment

US ACH

1916

Customer is redirected to this page to select their bank and login to their Internet banking profile to complete the transaction.

SA ACH

1147

Customer is redirected to this page to select their bank and login to their Internet banking profile to complete the transaction.

Implement webhooks

It is advised all merchants use webhooks to get automatic success confirmation on your transactions. To set up webhooks please visit the Webhooks section.

Step 5: Verify the payment

After charging a card successfully, you need to verify that the payment was successful with Rave before giving value to your customer on your website.

Below are the important things to check for when validating the payment:

Verify the transaction reference.

Verify the data.status of the transaction to be successful.

Verify the currency to be the expected currency

Most importantly validate the amount paid to be equal to or at least greater than the amount of the value to be given.

Below is sample code of how to implement server side validation in different programming languages

curl --request POST \
  --url https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/verify \
  --header 'content-type: application/json' \
  --data '{"txref":"MC-09182829","SECKEY":"FLWSECK-e6db11d1f8a6208de8cb2f94e293450e-X"}'
<?php 

$result = array();

$postdata =  array( 
  'txref' => 'MC-09182829',
  'SECKEY' => 'FLWSECK-bb971402072265fb156e90a3578fe5e6-X'
  );

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/verify");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,json_encode($postdata));  //Post Fields
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$headers = [
  'Content-Type: application/json',
];

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$request = curl_exec ($ch);
$err = curl_error($ch);

if($err){
	// there was an error contacting rave
  die('Curl returned error: ' . $err);
}


curl_close ($ch);

$result = json_decode($request, true);

if('error' == $result->status){
  // there was an error from the API
  die('API returned error: ' . $result->message);
}

if('successful' == $result->data->status && '00' == $result->data->chargecode){
  // transaction was successful...
  // please check other things like whether you already gave value for this ref
  // If the amount and currency matches the expected amount and currency etc.
  // if the email matches the customer who owns the product etc
  // Give value
}
//Endpoint to verify transaction
    private final String VERIFY_ENDPOINT = "https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/verify"; 
    
    /**
     * 
     * Method to 
     * 
     * @param paymententity - <b>paymententity - set as a constant with default value as 1</b>
     * @param txref - <b>txref - is the unique payment reference generated by the merchant.</b>
     * @param secret - <b>secret - is the merchant secret key</b>
     * @return
     * @throws UnirestException 
     */
    public JSONObject verify(String flwRef, String secret, double amount, int paymententity) throws UnirestException, Exception {
        
        // This packages the payload
        JSONObject data = new JSONObject();
        data.put("txref", txref);
        data.put("SECKEY", secret)
        
        // end of payload
        
        // This sends the request to server with payload
        HttpResponse<JsonNode> response = Unirest.post(VERIFY_ENDPOINT)
                .header("Content-Type", "application/json")
                .body(data)
                .asJson();
        
        // This get the response from payload
        JsonNode jsonNode = response.getBody();
        
        // This get the json object from payload
        JSONObject responseObject = jsonNode.getObject();
        
        // check of no object is returned
        if(responseObject == null)
            throw new Exception("No response from server");
        
        // This get status from returned payload
        String status = responseObject.optString("status", null);
        
        // this ensures that status is not null
        if(status == null)
            throw new Exception("Transaction status unknown");
        
        // This confirms the transaction exist on rave
        if(!"success".equalsIgnoreCase(status)){
            
            String message = responseObject.optString("message", null);
            
            throw new Exception(message);
        }
        
        data = responseObject.getJSONObject("data");
        
        // This get the amount stored on server
        double actualAmount = data.getDouble("amount");
        
        // This validates that the amount stored on client is same returned
        if(actualAmount != amount)
            throw new Exception("Amount does not match");
        
        
        // now you can give value for payment.
       
    }
var data = new {txref = "OH-AAED44", SECKEY = "FLWSECK-e6db11d1f8a6208de8cb2f94e293450e-X"};
            var client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var responseMessage = client.PostAsJsonAsync("https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/verify", data).Result;
            var responseStr = responseMessage.Content.ReadAsStringAsync().Result;
            var response = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseData>(responseStr);
            if (response.data.status == "successful" && response.data.amount == amount && response.data.chargecode == "00")
            {
              
              System.Console.WriteLine("Payment Successful then give value");
               
            }

When you successfully verify a completed payment see sample response below:

{
  "status": "success",
  "message": "Tx Fetched",
  "data": {
    "txid": 144534,
    "txref": "MC-09182829",
    "flwref": "FLWT000985573",
    "devicefingerprint": "N/A",
    "cycle": "one-time",
    "amount": 100,
    "currency": "NGN",
    "chargedamount": 100,
    "appfee": 0,
    "merchantfee": 0,
    "merchantbearsfee": 1,
    "chargecode": "02",
    "chargemessage": "Pending OTP validation",
    "authmodel": "AUTH",
    "ip": "::ffff:10.102.148.117",
    "narration": "Synergy Group",
    "status": "success-pending-validation",
    "vbvcode": "N/A",
    "vbvmessage": "N/A",
    "authurl": "NO-URL",
    "acctcode": null,
    "acctmessage": null,
    "paymenttype": "account",
    "paymentid": "2",
    "fraudstatus": "ok",
    "chargetype": "normal",
    "createdday": 4,
    "createddayname": "THURSDAY",
    "createdweek": 19,
    "createdmonth": 4,
    "createdmonthname": "MAY",
    "createdquarter": 2,
    "createdyear": 2018,
    "createdyearisleap": false,
    "createddayispublicholiday": 0,
    "createdhour": 9,
    "createdminute": 1,
    "createdpmam": "am",
    "created": "2018-05-10T09:01:52.000Z",
    "customerid": 24728,
    "custphone": "09090838390",
    "custnetworkprovider": "ETISALAT",
    "custname": "yemi alade",
    "custemail": "[email protected]",
    "custemailprovider": "COMPANY EMAIL",
    "custcreated": "2018-04-21T11:37:43.000Z",
    "accountid": 134,
    "acctbusinessname": "Synergy Group",
    "acctcontactperson": "Desola Ade",
    "acctcountry": "NG",
    "acctbearsfeeattransactiontime": 1,
    "acctparent": 1,
    "acctvpcmerchant": "N/A",
    "acctalias": "temi",
    "acctisliveapproved": 0,
    "orderref": "URF_1525942912124_3844735",
    "paymentplan": null,
    "paymentpage": null,
    "raveref": "RV31525942911654CA881BAE82",
    "account": {
      "id": 2,
      "account_number": "0690000031",
      "account_bank": "044",
      "first_name": "NO-NAME",
      "last_name": "NO-LNAME",
      "account_is_blacklisted": 0,
      "createdAt": "2016-12-31T04:09:24.000Z",
      "updatedAt": "2018-06-04T09:20:10.000Z",
      "deletedAt": null,
      "account_token": {
        "token": "flw-t0e1bb79f967612fc1-k3n-mock"
      }
    },
    "meta": []
  }
}