Payments Gateway, Off-ramp 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, Off-ramp use case?
With the Payment Gateway, Off-ramp use case, you or your customers can safely accept payments in stablecoins, cryptocurrencies, and settle to fiat or 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 create a new customer (merchant), open payment/settlement accounts for the customer, 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 a merchant
- Created payment and settlement accounts for the merchant
- 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 Layer2 Financial 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 our customer, Quorum Coffee.
2. Creating the merchant as a customer
The first step is to create a customer account for the merchant, in this case Quorum Coffee. It is this customer object that will be assigned:
- Crypto and fiat accounts
- Each of the payors
We are going to create a corporate customer using the customers
endpoint (You create an individual customer with the same API endpoint).
Note: The
customers
endpoint is for use by regulated entities. If you need Layer2 to perform KYC/KYB on your behalf, you will need to use theapplications
endpoint for onboarding. For this build guide, we will skip onboarding via theapplications
endpoint. See Applications for more information.
To create a corporate customer, the data you need is:
-
The customer
id
. This is the unique identifier of your customer within the Layer2 system. It must be unique to you. -
The
customer_type
. This isCORPORATION
. -
The
registered_name
of the corporation. This is the actual registered company name of the customer.
In this example, we’re going to onboard a new customer called Quorum Coffee. So we’ll set up their id
as QUORUMCOFFEE001 and their registered_name
as ‘Quorum Coffee Inc’.
Note: The
id
doesn’t have to include the company name. It just has to be unique. You can use any current customer identifier from your own system. See External Entity Identifiers for more information.
Each of those attributes can be added to a data object and POST
ed to the customers
API endpoint.
curl --location --request POST 'https://alpha.layer2financial.dev/api/v1/customers' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": "QUORUMCOFFEE001",
"customer_type": "CORPORATION",
"registered_name": "Quorum Coffee Inc."
}'
A successful request will return a status code of 200
and a response object with the id
of the new customer:
{
'data': {
'id': 'QUORUMCOFFEE001',
'customer_type': 'CORPORATION',
'registered_name': 'Quorum Coffee Inc.'
}
}
Once you’ve created a customer, you can retrieve information about that customer at any time using a GET
request to the same endpoint, appended with their CUSTOMER_ID
:
curl --location --request GET 'https://alpha.layer2financial.dev/api/v1/customers/QUORUMCOFFEE001' \
--header 'Authorization: Bearer {AUTH_TOKEN}' \
--header 'Content-Type: application/json'
The response object:
{
data: {
id: "QUORUMCOFFEE001",
customer_type: "CORPORATION",
registered_name: "Quorum Coffee Inc.",
}
}
Now we have our customer, the next step is to create payment accounts associated with that customer.
3. 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
from above. For this example that isQUORUMCOFFEE001
. -
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 aBASIC_PAYMENT
account for our USDC crypto account and aBASIC_DEPOSIT_FIAT
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_T
(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_T
.
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://alpha.layer2financial.dev/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": "BASIC_PAYMENT",
"asset_type_id": "ETHEREUM_GOERLI_USDC_T"
}
}
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://alpha.layer2financial.dev/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_T",
product_id: "BASIC_PAYMENT",
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://alpha.layer2financial.dev/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": "BASIC_DEPOSIT_FIAT",
"asset_type_id": "FIAT_TESTNET_USD_T"
}
}'
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://alpha.layer2financial.dev/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_T",
product_id: "BASIC_DEPOSIT_FIAT",
current_balance: 0,
available_balance: 0
}
}
That’s it for creating our accounts.
4. 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 Layer2 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.
5. 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://alpha.layer2financial.dev/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://alpha.layer2financial.dev/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_T',
'lease_enforced': False,
'lease_end_date': None,
'lease_transaction_limit': None,
'lease_value_limit': None
}
}
6. 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://alpha.layer2financial.dev/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_T",
product_id: "BASIC_PAYMENT",
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://alpha.layer2financial.dev/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://alpha.layer2financial.dev/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_T",
'product_id': 'BASIC_DEPOSIT_FIAT',
'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://alpha.layer2financial.dev/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://alpha.layer2financial.dev/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
. -
Setup a merchant with the
customers
endpoint . Then use thecustomer_id
to query or update information with thecustomers/{CUSTOMER_ID}
endpoint . -
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 Layer2 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 Layer2 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.