How to Accept Foreign Payments with Rapyd FX

By Amr Abdou

Rapyd Payment with FX is designed to address challenges associated with cross-border payments and make payment processing for global customers a seamless and transparent experience.

Processing online payments through the Rapyd API with FX provides customers a convenient checkout experience using their local payment methods, and the freedom to pay in the currency of their choice.

By the end of this tutorial, you’ll be able to use FX to do the following:

  • List a country’s available payment methods for a certain currency
  • Show exchange rates for that currency
  • Make requests to Rapyd to process payments in customers’ currencies

What Is Rapyd Payments with FX?

Payments with FX is a feature of the Rapyd platform that adds a layer of transparency to the payment processing of global businesses by showing customers the total amount to be charged throughout the checkout process. FX enables you to receive payments from your customers in the currency of their choice, regardless of whether it’s supported by the selected payment method.

Rapyd Payments with FX can be useful for online store owners, contractors, and anyone interested in expanding their business globally. Its use cases include:

  • Customers who would like to pay in their own currency to avoid cross-border payment currency conversion fees.
  • To show the prices of your products in multiple currencies with no hidden fees at checkout.
  • To provide a local checkout experience to your customers by supporting local payment methods that are only available in their region.

Implementing Rapyd Payments with FX

In the following sections, you will learn how to implement the Rapyd Payments with FX API to process foreign payments by completing the following actions:

  • Obtaining API keys
  • Setting up a Rapyd API request authorization
  • Retrieving the available payment methods for a specific currency
  • Getting the required fields for a payment method
  • Creating a payment request

You’ll complete these actions in an example scenario where a customer based in Singapore uses a credit card as a payment method, and wants to pay in Singaporean dollars while your store’s main currency is USD.

To follow this tutorial and start accepting payments in foreign currency, all you need is a Rapyd account. If you haven’t created your Rapyd account yet, you can sign up here.

The following diagram is a visual aid to help you understand the workflow of Rapyd Payments with FX from the start of the user checkout process to payment processing.

Obtaining the API Keys

Log in to your Rapyd account and activate the sandbox button at the bottom left corner of your account dashboard. Then, click View API keys to get your sandbox credentials. You can use these credentials during the development and testing phases.

Setting Up a Rapyd API Request Authorization

After creating your Rapyd account and obtaining your sandbox API keys, let’s create the functions that will handle the requests to Rapyd API.

Most of Rapyd’s API requests require five authorization headers:

  • access_key: Your sandbox API access key
  • content-Type: Indicates that the data appears in JSON format
  • salt: A salt string to create for the request signature
  • signature: A signature calculated for each request, which will be explained right after the following code sample
  • timestamp: This is the timestamp for the request in UNIX format

First, you’ll create the utilities file that contains the logic responsible for authorizing and sending requests to the Rapyd API servers. This tutorial uses the javascript programming language. If you need help, check out examples of the utilities file in other programming languages.

The utilities file includes the following four functions:

  • makeRequest(): Includes the logic to communicate with the Rapyd sandbox server
  • generateSignature(): Generates a Rapyd API request signature
  • generateRandomString(): Generates a salt string for the HTTP request
  • httpRequest(): Creates an HTTP request and returns the response

You can save the following code which contains the four functions in a separate file, and you will import it later in the examples to authorize the API requests.

The necessary code for the utilities file is as follows:

/*
* Environment Variables
*/
const https = require('https');
const crypto = require('crypto');
const accessKey = "<your-access-key>"; // Your API Access key
const secretKey = "<your-secret-key>"; // You API secret key
const log = false;


/*
* Make a Request to Rapyd API
*/
async function makeRequest(method, urlPath, body = null) {

  try {
    // API Request parameters
    httpMethod = method;
    httpBaseURL = "sandboxapi.rapyd.net";
    httpURLPath = urlPath; // The request path
    salt = generateRandomString(8); // Randomly created for each request
    idempotency = new Date().getTime().toString(); // Used for payment creation requests
    timestamp = Math.round(new Date().getTime() / 1000); 
    signature = generateSignature(httpMethod, httpURLPath, salt, timestamp, body) // Request Signature

    const options = {
      hostname: httpBaseURL,
      port: 443,
      path: httpURLPath,
      method: httpMethod,
      headers: {
        'Content-Type': 'application/json',
        salt: salt,
        timestamp: timestamp,
        signature: signature,
        access_key: accessKey,
        idempotency: idempotency
      }
    }

    return await httpRequest(options, body, log);
  }
  catch (error) {
    console.error("Error generating request options");
    throw error;
  }
}

/*
* Generate Request Signature
*/
function generateSignature(method, urlPath, salt, timestamp, body) {

  try {
    let bodyString = "";
    if (body) {
      bodyString = JSON.stringify(body);
      bodyString = bodyString == "{}" ? "" : bodyString;
    }

    let toSign = method.toLowerCase() + urlPath + salt + timestamp + accessKey + secretKey + bodyString;
    log && console.log(`toSign: ${toSign}`);

    let hash = crypto.createHmac('sha256', secretKey);
    hash.update(toSign);
    const signature = Buffer.from(hash.digest("hex")).toString("base64")
    log && console.log(`signature: ${signature}`);

    return signature;
  }
  catch (error) {
    console.error("Error generating signature");
    throw error;
  }
}

/*
* Generate a Random Salt String
*/
function generateRandomString(size) {
  try {
    return crypto.randomBytes(size).toString('hex');
  }
  catch (error) {
    console.error("Error generating salt");
    throw error;
  }
}

/*
* Create a HTTP Request
*/
async function httpRequest(options, body) {

  return new Promise((resolve, reject) => {

    try {

      let bodyString = "";
      if (body) {
        bodyString = JSON.stringify(body);
        bodyString = bodyString == "{}" ? "" : bodyString;
      }

      log && console.log(`httpRequest options: ${JSON.stringify(options)}`);
      const req = https.request(options, (res) => {
        let response = {
          statusCode: res.statusCode,
          headers: res.headers,
          body: ''
        };

        res.on('data', (data) => {
          response.body += data;
        });

        res.on('end', () => {

          response.body = response.body ? JSON.parse(response.body) : {}
          log && console.log(`httpRequest response: ${JSON.stringify(response)}`);

          if (response.statusCode !== 200) {
            return reject(response);
          }

          return resolve(response);
        });
      })

      req.on('error', (error) => {
        return reject(error);
      })

      req.write(bodyString)
      req.end();
    }
    catch(err) {
      return reject(err);
    }
  })

}


exports.makeRequest = makeRequest;

Creating these functions and saving the utilities file allows you to communicate with the Rapyd API servers.

The generateSignature() function creates the request signature. The signature is calculated as a concatenated hash of specific strings, using the following formula:

signature = BASE64 ( HASH ( http_method + url_path + salt + timestamp + access_key + secret_key + body_string ) )

Note: In the production environment, it’s recommended to use the variables declared at the top of the utilities file as environment variables, instead of declaring them in the source code itself.

Retrieving the Available Payment Methods for a Specific Currency

The Rapyd API has five payment method categories: card, bank redirect, bank transfer, e-wallet, and cash.

In the example scenario, your customer is paying by credit card in Singaporean dollars. So, you start by sending an API request to show the payment methods available for this currency.

In the following code examples, you use the makeRequest() function from the utilities file to authorize all the requests to the Rapyd API:

const makeRequest = require('<path-to-your-utility-file>/utilities').makeRequest;

async function getPaymentMethodsByCurrency(country, currency, paymentCategory) {
  try {
    const result = await makeRequest('GET', '/v1/payment_methods/country?country='+country+'&currency='+currency+'&category='+paymentCategory);

    console.log(result);
  } catch (error) {
    console.error('Error completing request', error);
  }
}

getPaymentMethodsByCurrency('SG', 'SGD', 'card');

You should then use the following request URL:

GET https://sandboxapi.rapyd.net/v1/payment_methods/country?country=SG&currency=SGD&category=card

The following is an example response after entering the above:

{
    "status": {
        "error_code": "",
        "status": "SUCCESS",
        "message": "",
        "response_code": "",
        "operation_id": "0ac1f924-3a23-4f67-ba44-6d6c883f61d4"
    },
    "data": [
        {
            "type": "sg_debit_mastercard_card",
            "name": "Mastercard Debit",
            "category": "card",
            "image": "https://iconslib.rapyd.net/checkout/sg_debit_mastercard_card.png",
            "country": "sg",
            "payment_flow_type": "",
            "currencies": [
                "SGD"
            ],
            "status": 1,
            "is_cancelable": true,
            "payment_options": [
                {
                    "name": "capture",
                    "type": "boolean",
                    "regex": "",
                    "description": "Determines when the payment is processed for capture.",
                    "is_required": false,
                    "is_updatable": false
                },
                {
                    "name": "complete_payment_url",
                    "type": "string",
                    "regex": "",
                    "description": "the complete_payment_url field must be filled in.",
                    "is_required": true,
                    "is_updatable": false
                },
                {
                    "name": "error_payment_url",
                    "type": "string",
                    "regex": "",
                    "description": "the error_payment_url field must be filled in.",
                    "is_required": true,
                    "is_updatable": false
                },
                {
                    "name": "statement_descriptor",
                    "type": "string",
                    "regex": "/^[a-zA-Z0-9]{0,22}/",
                    "description": "A text description suitable for a customer's payment statement. Limited to 22 characters.",
                    "is_required": false,
                    "is_updatable": false
                }
            ],
            "is_expirable": false,
            "is_online": false,
            "is_refundable": true,
            "minimum_expiration_seconds": 0,
            "maximum_expiration_seconds": 604800,
            "virtual_payment_method_type": "card",
            "is_virtual": false,
            "multiple_overage_allowed": false,
            "amount_range_per_currency": [
                {
                    "currency": "SGD",
                    "maximum_amount": null,
                    "minimum_amount": null
                }
            ],
            "is_tokenizable": false,
            "supported_digital_wallet_providers": []
        },
        {
            "type": "sg_credit_mastercard_card",
            "name": "Mastercard",
            "category": "card",
            "image": "https://iconslib.rapyd.net/checkout/sg_credit_mastercard_card.png",
            "country": "sg",
            "payment_flow_type": "",
            "currencies": [
                "SGD"
            ],
            "status": 1,
            "is_cancelable": true,
            "payment_options": [
                {
                    "name": "capture",
                    "type": "boolean",
                    "regex": "",
                    "description": "Determines when the payment is processed for capture.",
                    "is_required": false,
                    "is_updatable": false
                },
                {
                    "name": "complete_payment_url",
                    "type": "string",
                    "regex": "",
                    "description": "the complete_payment_url field must be filled in.",
                    "is_required": true,
                    "is_updatable": false
                },
                {
                    "name": "error_payment_url",
                    "type": "string",
                    "regex": "",
                    "description": "the error_payment_url field must be filled in.",
                    "is_required": true,
                    "is_updatable": false
                },
                {
                    "name": "statement_descriptor",
                    "type": "string",
                    "regex": "/^[a-zA-Z0-9]{0,22}/",
                    "description": "A text description suitable for a customer's payment statement. Limited to 22 characters.",
                    "is_required": false,
                    "is_updatable": false
                }
            ],
            "is_expirable": false,
            "is_online": false,
            "is_refundable": true,
            "minimum_expiration_seconds": 0,
            "maximum_expiration_seconds": 604800,
            "virtual_payment_method_type": "card",
            "is_virtual": false,
            "multiple_overage_allowed": false,
            "amount_range_per_currency": [
                {
                    "currency": "SGD",
                    "maximum_amount": null,
                    "minimum_amount": null
                }
            ],
            "is_tokenizable": false,
            "supported_digital_wallet_providers": []
        }
    ]
}


Note: The actual response will include more payment methods than this sample request.

Get the Required Fields for a Specific Payment Method

Assume that your customer has selected the first payment method from the last response result as their preferred payment method.

In this FX API request, you use the type value from the sg_credit_mastercard_card payment method in the data array from the previous response:

const makeRequest = require('<path-to-your-utility-file>/utilities').makeRequest;

async function getPaymentMethodRequiredFields(payment_method) {
  try {
    const result = await makeRequest('GET', '/v1/payment_methods/required_fields/'+payment_method);

    console.log(result);
  } catch (error) {
    console.error('Error completing request', error);
  }
}

getPaymentMethodRequiredFields('sg_credit_mastercard_card');

The request URL is as follows:

GET https://sandboxapi.rapyd.net/v1/payment_methods/required_fields/sg_credit_mastercard_card

The following is an example response after entering the above:

{
    "status": {
        "error_code": "",
        "status": "SUCCESS",
        "message": "",
        "response_code": "",
        "operation_id": "04e8db62-c81b-4f13-9b79-d9f6666890c8"
    },
    "data": {
        "type": "sg_credit_mastercard_card",
        "fields": [
            {
                "name": "name",
                "type": "string",
                "regex": "",
                "is_required": true,
                "instructions": "card holder name"
            },
            {
                "name": "number",
                "type": "string",
                "regex": "",
                "is_required": true,
                "instructions": "card number"
            },
            {
                "name": "expiration_month",
                "type": "string",
                "regex": "",
                "is_required": true,
                "instructions": "expiration month as string, 01-12"
            },
            {
                "name": "expiration_year",
                "type": "string",
                "regex": "",
                "is_required": true,
                "instructions": "expiration year in to digits as string, 18-99"
            },
            {
                "name": "cvv",
                "type": "string",
                "regex": "",
                "is_required": true,
                "instructions": "card cvv"
            }
        ],
        "payment_method_options": [
            {
                "name": "3d_required",
                "type": "boolean",
                "regex": "",
                "description": "Allows the client to determine whether the customer is required to complete 3DS authentication for the transaction",
                "is_required": false,
                "is_updatable": false
            }
        ],
        "payment_options": [
            {
                "name": "capture",
                "type": "boolean",
                "regex": "",
                "description": "Determines when the payment is processed for capture.",
                "is_required": false,
                "is_updatable": false
            },
            {
                "name": "complete_payment_url",
                "type": "string",
                "regex": "",
                "description": "the complete_payment_url field must be filled in.",
                "is_required": true,
                "is_updatable": false
            },
            {
                "name": "error_payment_url",
                "type": "string",
                "regex": "",
                "description": "the error_payment_url field must be filled in.",
                "is_required": true,
                "is_updatable": false
            },
            {
                "name": "statement_descriptor",
                "type": "string",
                "regex": "/^[a-zA-Z0-9]{0,22}/",
                "description": "A text description suitable for a customer's payment statement. Limited to 22 characters.",
                "is_required": false,
                "is_updatable": false
            }
        ],
        "minimum_expiration_seconds": 0,
        "maximum_expiration_seconds": 604800
    }
}

From this JSON response, you can see that the required fields are:

  • name
  • number
  • expiration_month
  • expiration_year
  • cvv

In the payment_method_options array, you can see that “3DS verification” is not required. If this field was required, it would mean that the credit card issuer uses 3DS verification, and your customer would be redirected to another URL to complete the extra verification.

Creating a Payment with Foreign Exchange

After selecting the payment method and the required fields, the final step is creating a payment with FX using the Rapyd API. You create a payment object for this FX payment API call.

To create a Rapyd payment with FX, you use the required fields from the previous response in addition to the payment data submitted by the customer, which include the following:

  • amount: The amount paid to the merchant
  • currency: The merchant’s currency
  • requested_currency: The currency requested by the customer
  • payment_method: An object containing the payment method and the customer’s payment details
  • expiration: Sets the time allowed for the customer to make this payment

The following is an example of code that could be used to create a payment with foreign exchange:

const makeRequest = require('<path-to-your-utility-file>/utilities').makeRequest;
async function createPayment(body_params = [], required_fields) {
  try {
    const body = {
      amount: body_params['amount'],
      currency: body_params['currency'],
      requested_currency: body_params['requested_currency'],
      fixed_side: body_params['fixed_side'],
      payment_method: {
        type: body_params['type'],
        fields: required_fields
      },
      expiration: Math.round(new Date().getTime() / 1000) + 900
    };
    const result = await makeRequest('POST', '/v1/payments', body);

    console.log(result);
  } catch (error) {
    console.error('Error completing request', error);
  }
}


let body_params = [];
body_params['amount'] = 100;
body_params['currency'] = 'USD';
body_params['requested_currency'] = 'SGD';
body_params['fixed_side'] = 'buy';
body_params['type'] = 'sg_credit_mastercard_card';

let fields = {
  name : 'John Doe',
  number : '4111111111111111',
  expiration_month : '07',
  expiration_year : '23',
  cvv : '123'
};

createPayment(body_params, fields);


In this case, the request body is as follows:

{
   "amount": 100,
   "currency": "USD",
   "expiration": 1591173527,
   "fixed_side": "buy",
   "payment_method": {
       "type": "sg_credit_mastercard_card",
       "fields": {
          "name": "John Doe",
           "number": "4111111111111111",
           "expiration_month": "07",
           "expiration_year": "23",
           "cvv": "123"
       }
   },
   "requested_currency": "SGD"
}

The following is the request URL:

// Request URL: POST https://sandboxapi.rapyd.net/v1/payments

Below is an example response after following the above steps:

{
    "status": {
        "error_code": "",
        "status": "SUCCESS",
        "message": "",
        "response_code": "",
        "operation_id": "4b257ecb-2775-4a3d-a829-978ead4d6dff"
    },
    "data": {
        "id": "payment_02e2ce0763a6473ef7788109ae5d0479",
        "amount": 136.53,
        "original_amount": 136.53,
        "is_partial": false,
        "currency_code": "SGD",
        "country_code": "SG",
        "status": "CLO",
        "paid": true,
        "paid_at": 1583308522,
        "payment_method_type": "sg_credit_mastercard_card",
        "payment_method_type_category": "card",
        "fx_rate": "0.73242372",
        "merchant_requested_currency": "USD",
        "merchant_requested_amount": 100,
        "fixed_side": "buy",
        "payment_fees": null,
        "invoice": "",
        "escrow": null,
        "group_payment": ""
    }
}

The example function takes two arguments:

  • body_params: An array of the body parameters
  • fields: An array of the required fields

The expiration value is set to the current timestamp plus fifteen minutes. This value sets the period in which the customer is allowed to make the payment.

The response data array displays the following:

  • id: A unique payment ID for reference
  • amount: The amount paid by the customer, after conversion
  • merchant_requested_amount: The amount received by the merchant
  • fx_rate: The exchange rate at the time of the payment creation
  • paid_at: The UNIX timestamp when the payment was completed

Getting Daily FX Rates Using Rapyd Rates API

The daily rate object contains the daily conversion rate of currencies for both payments and payouts. All the rates returned include the FX markup fees.

In the example scenario, you want to display the product prices in your customer’s local currency so your customer can see the final price that they’ll pay at checkout. To do this, you query the daily rate object to fetch the daily exchange rates for a specific currency and update your website accordingly:

const makeRequest = require('<path-to-your-utility-file>/utilities').makeRequest;

async function getDailyRates(buy_currency, sell_currency) {
  try {
    const result = await makeRequest( "GET", "/v1/rates/daily?action_type=payment&sell_currency=" + sell_currency +"&buy_currency=" + buy_currency);

    console.log(result);
  } catch (error) {
    console.error('Error completing request', error);
  }
}

getDailyRates('USD', 'SGD');

In this case, the request URL is the following:

GET https://sandboxapi.rapyd.net/v1/rates/daily?action_type=payment&buy_currency=SGD&sell_currency=USD 

You should receive a response like the following example:

{
    "status": {
        "error_code": "",
        "status": "SUCCESS",
        "message": "",
        "response_code": "",
        "operation_id": "d95d431d-0743-4a00-9194-ba94f254409e"
    },
    "data": {
        "sell_currency": "USD",
        "buy_currency": "SGD",
        "fixed_side": null,
        "action_type": "payment",
        "rate": 1.3462533,
        "date": "2022-06-12",
        "sell_amount": null,
        "buy_amount": null
    }
}

You only used three parameters in the example function:

  • action_type: Can be either payment or payouts
  • buy_currency: SGD in our scenario
  • sell_currency: Set to USD

Rapyd’s FX daily rate includes three more query parameters: amount, fixed_side, and date. The date parameter (in YYYY-MM-DD format) specifies the FX rates for a specific date, while fixed_side and amount specify the conversion rate for a fixed amount of the buy_currency or sell_ currency.

You can use the following example request to get the FX rate for 100 USD on the June 5th, 2022:

GET https://sandboxapi.rapyd.net/v1/rates/daily?action_type=payment&buy_currency=SGD&sell_currency=USD&fixed_side=sell&amount=100&date=2021-06-05

The example here is as follows:

{
    "status": {
        "error_code": "",
        "status": "SUCCESS",
        "message": "",
        "response_code": "",
        "operation_id": "daa69ab6-2b87-421e-bbcc-46394fd452c7"
    },
    "data": {
        "sell_currency": "USD",
        "buy_currency": "SGD",
        "fixed_side": "sell",
        "action_type": "payment",
        "rate": 1.2846389,
        "date": "2021-06-05",
        "sell_amount": 100,
        "buy_amount": 128.46
    }
}

Conclusion

By following this tutorial, you completed the first steps to accepting payments from your global customers through their local payment methods. Specifically, you learned how to do the following:

  • Set up request authorization for Rapyd API calls
  • Use the Rapyd API to get the available payment methods for a specific currency in a specific country
  • Use the Rapyd API to get the required fields for the selected payment method
  • Create a payment with FX
  • Update the daily exchange rates

The Rapyd API is designed to help businesses accept local payment methods, expand into new regions, and build better cash flow management.

Aside from accepting foreign payments, Rapyd can also help with a diverse range of global payment challenges, including mass disbursements, building hosted e-commerce checkout pages, receiving and managing escrow payments, managing global subscriptions, and recurring billing.

2 Likes