Handling Deposits

Phase 1: Receiving Deposit Request from a User

Step 1

The user should send the deposit request to the server. The deposit request should have the currency and amount being transferred.

// Express post endpoint to receive deposit requests
app.post(
'/plugins/bank/deposit',
toolsLib.security.verifyBearerTokenExpressMiddleware(['user']),
(req, res) => {
...
}
);

Step 2

The server creates a unique key (UUID) for the deposit. This UUID will ensure that this transaction occurs only once.

After creating the UUID, the deposit data (amount, currency, user ID, UUID) should be stored on Redis. We recommend setting the UUID as the key. It's also a good idea to give the data an expiry time.

const transferUuid = // generate uuid
const amount, currency = // deposit data from request
const user_id = // ID of user making request
const payload = {
amount,
currency,
user_id
};
// store transfer data on redis with uuid as key
await toolsLib.database.client.setAsync(transferUuid, JSON.stringify(payload));

Step 3

After the deposit is given a UUID and is stored on Redis, the server should then forward the payment request to the payment service. The payment service should receive the amount, currency, and UUID of the requested deposit.

Step 4

Once the request from the server is received, the payment service will prompt the user to complete the payment. This can be handled by the server or by the payment service directly via a redirect.

This step will differ based on which service is used but will ultimately end up with the user being asked to complete the payment.

Phase 2: User completes deposit

Phase 3: Payment service notifies the server of completed deposit

Step 1

Once the user completes the payment, the service will notify the server of the completed payment. The way notifications are handled will depend on the payment service used. This notification should come with the amount and currency of the completed deposit. It should also provide the UUID the server created for this deposit in Phase 1.

// Express post endpoint to receive deposit notifications
// Accepts uuid as path parameter
app.post(
'/plugins/bank/:uuid',
(req, res) => {
...
}
);

Step 2

Once the server receives the notification, it should first check Redis to make sure the UUID is valid.

If no data is found on Redis with the received UUID, the deposit should be rejected.

If data is found with the UUID, that data should be deleted from Redis. After deletion, the server should verify that the amount and currency of the deposit received from the payment service matches the ones found on Redis.

If the user paid less than the required amount or used a different currency, the deposit should be rejected.

const uuid = // uuid received in notification
const amount, currency = // deposit data from notification
// get transfer data from Redis using uuid
const storedDepositData = await toolsLib.database.client.getAsync(uuid);
// if no data found on Redis, reject transfer
if (!storedDepositData) {
throw err;
}
// Delete data from Redis
await toolsLib.database.client.delAsync(uuid);
// If deposit amount or currency don't match data found on redis, reject transfer
if (
storedDepositData.amount !== amount
|| storedDepositData.currency !== currency
) {
throw err;
}

Step 3

Once everything is validated, the server should mint the asset to the user. This completes the deposit flow.

Code Example

For this example, we will use a redirect for prompting the user to complete the payment and receive a notification of the completed payment via a callback URL. The process for both may be different for some payment services but the overall flow should remain the same.

// PHASE 1: RECEIVE DEPOSIT REQUEST FROM USER
app.post('/plugins/bank/deposit', toolsLib.security.verifyBearerTokenExpressMiddleware(['user']), (req, res) => {
const { currency, amount } = req.body;
const user_id = req.auth.sub.id;
// Create UUID for this deposit
const transactionId = uuid();
// Callback url for payment notification
const callback_url = `${toolsLib.getKitConfig().info.url}/plugins/bank/${transactionId}`;
const payload = {
amount,
currency,
callback_url,
user_id, // user_id added for callback
expires: moment().add(5, 'minutes') // if payment occurs 5 minutes after expires, deny transfer
};
// Store deposit data in Redis
await toolsLib.database.client.setAsync(transferUuid, JSON.stringify(payload));
// PAYMENT_SERVICE is the payment service being used
const transferResponse = await PAYMENT_SERVICE.deposit(payment);
// Redirect user for completing the payment
return res.redirect(transferResponse.url);
});
// PHASE 3: RECEIVE NOTIFICATION OF COMPLETED DEPOSIT FROM PAYMENT SERVICE
app.post('/plugins/bank/:uuid', (req, res) => {
const { uuid } = req.params;
const { amount, currency, transaction_id } = req.body;
// Get deposit data from Redis via uuid
const storedDepositData = await toolsLib.database.client.getAsync(uuid);
// If data is not found, reject deposit
if (!storedDepositData) {
return res.status(400).json({ message: `No deposit found with UUID ${uuid}` });
}
storedDepositData = JSON.parse(storedDepositData);
// Delete data from Redis
await toolsLib.database.client.delAsync(uuid);
// If deposit occured after expiry time, reject the deposit
if (moment().isAfter(storedDepositData.expires)) {
return res.status(400).json({ message: 'Deposit is expired' });
}
// If deposit amount or currency don't match ones stored on Redis, reject the deposit
if (amount !== storedDepositData.amount || currency !== storedDepositData.currency) {
return res.status(400).json({ message: 'Incorrect amount or currency given' });
}
// All checks passed, mint the asset with the transaction ID obtained from serivce provider
const mintResponse = await toolsLib.wallet.mintAssetByKitId(
storedDepositData.user_id, // User kit id
storedDepositData.currency, // currency
storedDepositData.amount, // amount
{
description: 'Bank Deposit', // description
transactionId: transaction_id // transaction id
}
);
return res.json(mintResponse);
});