Payments Gateway Use Case Build Guide

If you haven’t already, Sign Up for Sandbox Access to get your client ID and secret to work through this Build Guide!

What is the Payment Gateway use case?

With the Payment Gateway use case, you or your customers can safely accept payments in stablecoins, cryptocurrencies, and settle to fiat or another digital currency of choice. Create invoices, add payors and track payments in an automated fashion.

path

Build Guide

This guide should take no longer than 10 minutes and will allow you to execute a typical payment gateway flow

Note: In sandbox, auto-wire won't work. In production, the wire is automatically sent to a linked fiat account that is setup during onboarding.

You’ll open payment/settlement accounts for a corporate customer we have pre-created for you, setup a payor, generate a deposit address for the payor and then transfer in the funds. The platform handles the rest including the allocation, exchange, settlement, wiring, AML, Fraud, wallets and gas fees. By the end of this guide you will have:

  1. Authenticated your request
  2. Created payment and settlement accounts for a corporate customer
  3. Linked the payment and settlement accounts
  4. Created a payor and generated a payment address
  5. Received crypto payment and auto-settled into fiat

Let’s go!

1. Authenticating your request

Every request you make to a Rail API endpoint requires an AUTH_TOKEN. We secure our endpoints using standards based OAuth2 Client Credentials Grant and scopes. This makes authenticating secure and easy.

To obtain the AUTH_TOKEN , you will need to authorize your account using your BASE_64_ENCODED_CLIENTID_AND_SECRET with the scopes you require:

Copy
Copied
curl --location --request POST 'https://auth.layer2financial.com/oauth2/ausbdqlx69rH6OjWd696/v1/token?grant_type=client_credentials&scope=customers:read+customers:write+accounts:read+accounts:write+exchanges:read+exchanges:write' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cache-Control: no-cache' \
--header 'Authorization: Basic {BASE_64_ENCODED_CLIENTID_AND_SECRET}' \

This gives you a response object containing your AUTH_TOKEN:

Copy
Copied
{
	"token_type":"Bearer",
	"expires_in":43200,
	"access_token": AUTH_TOKEN,
	"scope":"customers:read customers:write accounts:write accounts:read exchanges:read exchanges:write"
}

The scopes we’ll need for this tutorial are:

  • customers:read and customers:write so we can create our customer and get information about them.
  • accounts:read and accounts:write so we can create our accounts and get information about them.
  • transfers:read and transfers:write so we can create our transfers and get information about them.
  • exchanges:read and exchanges:write so we can create our exchanges and get information about them

The full list of scopes are available here.

With that done, we can get on to setting up accounts for a corporate customer we have pre-created for you, Quorum Coffee, with a customer_id of QUORUMCOFFEE001. To learn how to create a customer, check out Applications.

2. Creating the payment and settlement accounts

We need two accounts, a crypto payment account and a fiat deposit account for fiat settlement:

  1. When a customer of Quorum Coffee buys a coffee they can pay in USDC. This USDC goes into Quorum Coffee’s crypto payment account .
  2. This crypto is then auto-traded to a fiat deposit account

The settlement process then occurs every evening and the funds are withdrawn from the fiat deposit account to an external account.

Note: The settlement and wiring steps are only active in production.

To create these two accounts we need:

  • The customer_id for Quorum Coffee, QUORUMCOFFEE001 .
  • An account_id for each account that will be the unique identifier for that specific account. Here we’ll use QUORUMCOFFEE001_USDC.001 as our crypto account id and QUORUMCOFFEE001_USD.001 for our fiat account.
  • The product_id : This is the type of account to be opened. You can configure your own products, but for our accounts we want to set up a  PAYMENT_FORT_CRYPTO account for our USDC crypto account and a DEPOSIT_BASIC account for our USD fiat account.
  • The asset_type_id : The asset type that the accounts are going to use. Our crypto account is going to be denominated in USDC, so the asset_type_id is ETHEREUM_GOERLI_USDC ( asset_type_id s are in the form of {BLOCKCHAIN}_{NETWORK}_{CURRENCY_CODE} ). Our fiat account is in USD, so the asset type id is FIAT_TESTNET_USD .

Creating our Crypto account

With all that information, we can set up our crypto account first. We’ll pass the information set out above in a data object and POST to our payments accounts API endpoint:

Copy
Copied
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/payments' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
	"customer_id": "QUORUMCOFFEE001",
	"account_to_open": {
        "account_id": "QUORUMCOFFEE001_USDC.001",
        "product_id": "PAYMENT_FORT_CRYPTO",
        "asset_type_id": "ETHEREUM_GOERLI_USDC"
    }
}

The response contains the account_id that you passed in :

Copy
Copied
{
	data: {
		id: "QUORUMCOFFEE001_USDC.001"
	}
}

You can use that ACCOUNT_ID and a GET request to the /accounts/payments/ endpoint to retrieve information about this account:

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/payments/QUORUMCOFFEE001_USDC.001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response object will have the status of the account, product and asset type ids, and the current and available balance:

Copy
Copied
{
  data: {
    id: "QUORUMCOFFEE001_USDC.001",
    status: "OPEN",
    asset_type_id: "ETHEREUM_GOERLI_USDC",
    product_id: "PAYMENT_FORT_CRYPTO",
    current_balance: 0,
    available_balance: 0
  }
}

Creating our fiat account

For our fiat account, we’re going to call the deposits account API endpoint:

Copy
Copied
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/deposits' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
	"customer_id": "QUORUMCOFFEE001",
	"account_to_open": {
        "account_id": "QUORUMCOFFEE001_USD.001",
        "product_id": "DEPOSIT_BASIC",
        "asset_type_id": "FIAT_TESTNET_USD"
    }        
}'

The response contains the account_id that you passed in :

Copy
Copied
{
	data: {
		id: "QUORUMCOFFEE001_USD.001"
	}
}

Again, we can use that account_id and a GET request to the accounts/deposits/${account_id} endpoint to retrieve information about this account:

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/QUORUMCOFFEE001_USD.001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response object will have the status of the account, product and asset type ids, and the current and available balance:

Copy
Copied
{
  data: {
    id: "QUORUMCOFFEE001_USD.001",
    status: "OPEN",
    asset_type_id: "FIAT_TESTNET_USD",
    product_id: "DEPOSIT_BASIC",
    current_balance: 0,
    available_balance: 0
  }
}

That’s it for creating our accounts.

3. Linking the accounts

We now have to link the 2 accounts together. We want them linked so that when a the crypto is deposited into the QUORUMCOFFEE001_USDC.001 USDC account, it is auto-exchanged into the QUORUMCOFFEE001_USD.001 USD account, auto-settled each evening and then auto-wired out the following morning.

NOTE: To link your accounts together, you can reach out to the Rail team via our website or support@layer2financial.com. The ability to do this yourself is coming in Q1, 2023.

That completes the basic setup. Now its time to transact.

4. Creating your payor and generating a payment address

Next up we have to create payors for the customer. In our scenario, they are the people buying the cup of Quorum coffee.

Payors are assigned to customers, so to create a new payor we need the customer_id from above, and an id for the payor. As with customers and accounts, it makes sense to use External Entity Identifiers for the id for payors:

Copy
Copied
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/customers/QUORUMCOFFEE001/payor' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
	"id": "qc_payor_001",
	"first_name": "Jack",
	"last_name": "Smith"
}'

The response object will be the payor_id of the new payor:

Copy
Copied
{
	'data': {
		'payor_id': 'qc_payor_001'
	}
}

Once you have a payor, then you can get a payment address. This is the address any payment by that payor is going to go to. We need three pieces of information to get this address:

  • The account_id for our crypto account. In this case, that is QUORUMCOFFEE001_USDC.001 .
  • The payment amount . We’ll suppose we’re buying a $5 coffee here, but paying in the equivalent 5 USDC
  • The payor_id , which here is ‘qc payor 001’

You can also optionally add a payment_reference, such as an invoice number, again for easier internal reconciliation by the merchant. POST this data to the payments endpoint:

Copy
Copied
curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/payments/QUORUMCOFFEE001_USDC.001/address' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "5",
"payor_id": "qc_payor_001",
"payment_reference": "00000001"
}'

This will return an object with the wallet_address that will be passed on to the payors. The wallet_address is the main attribute there, but we can also see information about our asset type and currency. We can also get information about the ‘lease’ on that particular address. The lease is important in considering which type of addresses the payors need for any given customer:

  • If lease_enforced is set to False , you get a fixed address for each payor account. This payor will always have the same wallet throughout their lifetime as a customer of the merchant. This is useful for recurring payors.
  • lease_enforced is set to True rotating addresses will be used. Rotating addresses help optimize blockchain fees and work best when an address is for single time usage.

Here lease_enforced is set to False so we have a fixed address for this customer (though in a coffee shop scenario, a rotating address would also work).

Copy
Copied
{
	'data': {
		'id': '619662b2-51de-46c1-96e7-cd46d3b87ff0', 
		'wallet_address': '0x91C9F15E5Aa93DE0B520c9E2Ced837c2Cb1bb9c3', 
		'signed_wallet_address': None, 
		'asset_type': 'CRYPTO', 
		'currency': 'USDC_TG', 
		'asset_type_id': 'ETHEREUM_GOERLI_USDC', 
		'lease_enforced': False, 
		'lease_end_date': None, 
		'lease_transaction_limit': None, 
		'lease_value_limit': None
	}
}

5. Receiving your payments and exchanging your currencies

To test this out, all we need is the wallet_address from above to send a 5 USDC payment in on behalf of the payor, Jack Smith.

With the wallet_address, you can deposit funds into this account using any Goerli faucet, such as the Alchemy Goerli faucet (requires signup) or All That Node (no signup needed). You can also send a note at support@layer2financial.com.

Once you’ve deposited funds, you can check they are in the account by querying the accounts/payments/{ACCOUNT_ID} endpoint again:

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/payments/QUORUMCOFFEE001_USDC.001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The current_balance should have changed to the amount you added to the account:

Copy
Copied
{
  data: {
    id: "QUORUMCOFFEE001_USDC.001",
    status: "OPEN",
    asset_type_id: "ETHEREUM_GOERLI_USDC",
    product_id: "PAYMENT_FORT_CRYPTO",
    current_balance: 5.000000000000000000,
    available_balance: 0
  }
}

If the available_balance is showing 0 as above, the transaction is still going through all the AML and Fraud checks. You can check on the status of the transactions by querying the accounts/payments/{ACCOUNT_ID}/transactions endpoint.

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/payments/QUORUMCOFFEE001_USDC.001/transactions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response object will tell us what we need to know about our transactions:

Copy
Copied
{
	'data': {
		'transactions': [
			{
                'payor_id:'qc_payor_001',
                'payor_amount':5,
                'payor_reference_id':'00000001','external_transaction_id':'0xd0eba9c73dd9z31f0289c4631672c2438f54a40df359544d1775440f4dcae6d27','id':'3b8e38ad-ba09-46e8-9a40-fed2acb12956',
                'value':5.000000000000000000
				'transaction_date': '2022-11-09T09:46:55.519763', 
				'transaction_posted_date': '2022-11-09T09:46:55.519763', 
				'transaction_status': 'PENDING', 
				'description': 'TRANSFER_IN', 
				'transaction_type': 'TRANSFER_IN'
			}
		]
	}
}

As we can see, our transaction_status is still PENDING. Once the transaction is ACCEPTED, we’ll see the funds in our available_balance. You may find that your transaction was already POSTED by the time you have checked. That’s ok - we’re just moving faster than we planned!

Once the available_balance is updated, the USDC will be auto-traded into USD into the fiat account, QUORUMCOFFEE001_USD.001. After a few minutes you’ll be able to GET that information with the account_id using the accounts/deposits/{$account_id} endpoint

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/QUORUMCOFFEE001_USD.001' \
--header 'Authorization: Bearer {$AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response object shows the payment has been applied:

Copy
Copied
{
  'data': {
   'id': 'QUORUMCOFFEE001_USD.001',
    'status': 'OPEN',
    'asset_type_id': 'FIAT_TESTNET_USD',
    'product_id': 'DEPOSIT_BASIC',
    'current_balance': 5,
    'available_balance': 5
  }
}

You can check on the status of the transactions by querying the accounts/payments/${account_id}/transactions endpoint. If we call it for our USDC account, we’ll see both the transfer in from the payor, and the trade out to our fiat account:

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/payments/QUORUMCOFFEE001_USDC.001/transactions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response gives us both the TRANSFER_IN and the TRADE out:

Copy
Copied
{
	data: {
		'transactions': [
			{
				'payor_id': 'qc_payor_001', 
				'payor_amount': 5, 
				'external_transaction_id': '0xd0eba9c73dd9z31f0289c4631672c2438f54a40df359544d1775440f4dcae6d27', 
				'id': '5c104b2c-b845-496b-9813-539a26f92920', 
				'value': 5.0, 
				'transaction_date': '2022-11-19T14:01:42.365466', 
				'transaction_posted_date': '2022-11-19T14:05:40.923995', 
				'transaction_status': 'POSTED', 
				'description': 'TRANSFER_IN', 
				'transaction_type': 'TRANSFER_IN'
			}, 
            {
				'id': 'e23a56a9-edc0-499a-aa14-32e6f0b3ae0b', 
				'value': -5.0, 
				'transaction_date': '2022-11-19T14:47:03.437104', 
				'transaction_posted_date': '2022-11-19T14:47:26.295521', 
				'transaction_status': 'POSTED', 
				'description': 'TRADE', 
				'transaction_type': 'TRADE'
			}
		]
	}
}

When we call it for our USD fiat account using the accounts/deposits/${account_id}/transactions endpoint, we see just the TRADE in:

Copy
Copied
curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/QUORUMCOFFEE001_USD.001/transactions' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'

The response:

Copy
Copied
{
	'data': {
		'transactions': [
			{
				'id': '1e28d03b-1bfe-4a91-82e5-b76bea658e94', 
				'value': 5, 
				'transaction_date': '2022-11-19T14:47:26.295521', 
				'transaction_posted_date': '2022-11-19T14:47:26.295521', 
				'transaction_status': 'PENDING', 
				'description': 'TRADE', 
				'transaction_type': 'TRADE'
			}
		]
	}
}

Once the transaction is auto-settled, the transaction_status will change to POSTED. In production, settlement happens nightly during the weekday and the funds are automatically wired out the next business day.

That’s it. The merchant is set up to receive payments from their customers.

Summary

Let’s review:

  1. Authenticate using the OAuth Endpoint to get the AUTH_TOKEN .
  2. Create the 2 accounts, the USDC one to receive the payment and a USD one to settle into. To setup the USDC account, use the accounts/payments endpoint . To setup the USD settlement account, use the accounts/deposits endpoint .
  3. Link the 2 accounts by reaching out to the Rail team.
  4. Setup a payor using the customers/{CUSTOMER_ID}/payor endpoint and generate a payment wallet_address for the USDC account using the accounts/payments/{ACCOUNT_ID}/address .
  5. Deposit funds into the USDC account wallet_address using external faucets and query the balance of the the account using the accounts/payments/{ACCOUNT_ID} endpoint . Funds are auto-exchanged into the USD account. Check transactions in the USD account using the accounts/deposits/{ACCOUNT_ID}/transactions endpoint .

To dive deeper into what you can do on the Rail platform, head to our API documentation.

Contact Us - We’d love to hear your thoughts, and you can contact the team via slack, website or email support@layer2financial.com.

© 2024 Rail. All Rights Reserved.