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 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:readandcustomers:writeso we can create our customer and get information about them. -
accounts:readandaccounts:writeso we can create our accounts and get information about them. -
transfers:readandtransfers:writeso we can create our transfers and get information about them. -
exchanges:readandexchanges:writeso 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_idfor Quorum Coffee,QUORUMCOFFEE001. -
An
account_idfor each account that will be the unique identifier for that specific account. Here we’ll useQUORUMCOFFEE001_USDC.001as our crypto account id andQUORUMCOFFEE001_USD.001for 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_CRYPTOaccount for our USDC crypto account and aDEPOSIT_FORT_FIATaccount 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_idisETHEREUM_GOERLI_USDC(asset_type_ids 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_FORT_FIAT",
"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_FORT_FIAT",
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 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.
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_idfor 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_enforcedis 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_enforcedis set toTruerotating 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_FORT_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://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/paymentsendpoint . To setup the USD settlement account, use theaccounts/depositsendpoint . - Link the 2 accounts by reaching out to the Layer2 team.
-
Setup a payor using the
customers/{CUSTOMER_ID}/payorendpoint and generate a paymentwallet_addressfor the USDC account using theaccounts/payments/{ACCOUNT_ID}/address. -
Deposit funds into the USDC account
wallet_addressusing 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}/transactionsendpoint .
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.