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.
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:
- Authenticated your request
- Created payment and settlement accounts for a corporate customer
- Linked the payment and settlement accounts
- Created a payor and generated a payment address
- 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:
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:
{
"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
andcustomers:write
so we can create our customer and get information about them. -
accounts:read
andaccounts:write
so we can create our accounts and get information about them. -
transfers:read
andtransfers:write
so we can create our transfers and get information about them. -
exchanges:read
andexchanges: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:
- When a customer of Quorum Coffee buys a coffee they can pay in USDC. This USDC goes into Quorum Coffee’s crypto payment account .
- 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 useQUORUMCOFFEE001_USDC.001
as our crypto account id andQUORUMCOFFEE001_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 aPAYMENT_FORT_CRYPTO
account for our USDC crypto account and aDEPOSIT_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 theasset_type_id
isETHEREUM_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 isFIAT_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:
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 :
{
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:
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:
{
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:
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 :
{
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:
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:
{
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:
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:
{
'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 isQUORUMCOFFEE001_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:
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 toFalse
, 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 toTrue
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).
{
'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:
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:
{
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.
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:
{
'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
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:
{
'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:
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:
{
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:
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:
{
'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:
-
Authenticate using the OAuth Endpoint to get the
AUTH_TOKEN
. -
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 theaccounts/deposits
endpoint . - Link the 2 accounts by reaching out to the Rail team.
-
Setup a payor using the
customers/{CUSTOMER_ID}/payor
endpoint and generate a paymentwallet_address
for the USDC account using theaccounts/payments/{ACCOUNT_ID}/address
. -
Deposit funds into the USDC account
wallet_address
using external faucets and query the balance of the the account using theaccounts/payments/{ACCOUNT_ID}
endpoint . Funds are auto-exchanged into the USD account. Check transactions in the USD account using theaccounts/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.