When dealing with financial transactions, it’s crucial to handle scenarios where additional verification or processing time is needed.

Blnk’s Inflight feature provides a powerful tool for managing transactions that require additional steps or approvals before being finalized. This feature allows you to hold funds without applying the transaction until certain conditions are met.

Overview of Blnk Inflight

Inflight is a transaction parameter that instructs Blnk to hold on applying a transaction until a specified condition has been met. This condition could be confirming that it has been processed by your payment partners or custom conditions set by your application.

Key benefits of using Inflight:

  • Manages transactions that may take time to complete.
  • Ensures funds are not available for spending until the transaction is finalized.
  • Eliminates the need for refunds when partners return a failed response.

Example: Using Inflight for a payment requiring additional information

Let’s consider a scenario where we need to transfer $750.00 from Customer A’s USD wallet to an external payment provider, but the payment requires additional verification before being finalized. We’ll use the Inflight feature to manage this process.

First, let’s initiate the transaction with the Inflight flag:

NodeJS
const axios = require('axios');

async function createInflightTransaction() {
  try {
    const response = await axios.post('http://localhost:5001/transactions', {
      amount: 750.00,
      precision: 100,
      reference: "ref_001adcfgf",
      description: "Payment to ExternalProvider (Pending Verification)",
      currency: "USD",
      source: "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
      destination: "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
      inflight: true,
      inflight_expiry_date: "2024-02-03T12:38:19Z",
      meta_data: {
        provider_reference: "ext-pay-123456",
        customer_id: "1234",
        verification_status: "PENDING"
      }
    }, {
      headers: {
        'X-Blnk-Key': 'your-blnk-api-key-here',
        'Content-Type': 'application/json'
      }
    });

    console.log('Inflight transaction created:', response.data);
    return response.data;
  } catch (error) {
    console.error('Error creating inflight transaction:', error.response.data);
    throw error;
  }
}

createInflightTransaction();
Expected response
{
    "transaction_id": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
    "source": "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
    "destination": "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
    "reference": "ref_001adcfgf",
    "parent_transaction": "",
    "amount": 750.00,
    "precise_amount": 75000,
    "rate": 0,
    "precision": 100,
    "currency": "USD",
    "description": "Payment to ExternalProvider (Pending Verification)",
    "status": "INFLIGHT",
    "hash": "032af5e26c8a2c2690f1bb70bdd7c044bbdf9c7f5576fc0f693200baf65a9ee3",
    "allow_overdraft": false,
    "inflight": true,
    "created_at": "2024-02-20T05:28:03Z",
    "scheduled_for": "0001-01-01T00:00:00Z",
    "inflight_expiry_date": "2024-02-03T12:38:19Z",
    "meta_data": {
        "provider_reference": "ext-pay-123456",
        "customer_id": "1234",
        "verification_status": "PENDING"
    }
}

How Inflight works

When you enable inflight on a transaction:

  1. The transaction is added to the queue with status QUEUED.
  2. Once ready for processing, the status changes to INFLIGHT.
  3. The inflight balances of the participating ledger balances (source and destination) are updated:
    • inflight_credit_balance: Total amount waiting to be credited.
    • inflight_debit_balance: Total amount waiting to be debited.
  4. The amount is debited from the source’s inflight_debit_balance and credited to the destination’s inflight_credit_balance.
  5. The main balances of the source and destination remain untouched until the transaction is finalized.

Handling webhooks and automating inflight transaction resolution

Let’s set up a simple Express.js server to handle webhooks and automate the Inflight transaction resolution process:

NodeJS
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');

const app = express();
app.use(bodyParser.json());

// Webhook endpoint
app.post('/payment-webhook', async (req, res) => {
  const { status, providerReference, amount, currency } = req.body;

  console.log(`Received webhook: Status ${status} for payment ${providerReference}`);

  try {
    if (status === 'VERIFIED') {
      console.log(`Payment ${providerReference} verified. Committing transaction.`);
      await commitInflightTransaction(providerReference);
    } else if (status === 'REJECTED') {
      console.log(`Payment ${providerReference} rejected. Voiding inflight transaction.`);
      await voidInflightTransaction(providerReference);
    }
    res.sendStatus(200);
  } catch (error) {
    console.error('Error processing webhook:', error);
    res.status(500).send('Error processing webhook');
  }
});

async function findTransactionId(providerReference) {
  // In a real-world scenario, you would query your database here
  // This is a placeholder implementation
  return "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d";
}

async function commitInflightTransaction(providerReference) {
  const transactionId = await findTransactionId(providerReference);

  try {
    const response = await axios.post(`http://localhost:5001/transactions/${transactionId}/inflight`, {
      status: "commit",
      meta_data: {
        verification_status: "VERIFIED",
        provider_reference: providerReference
      }
    }, {
      headers: {
        'X-Blnk-Key': 'your-blnk-api-key-here',
        'Content-Type': 'application/json'
      }
    });

    console.log('Transaction committed successfully:', response.data);
    // Here you might want to update your internal database, notify the customer, etc.
  } catch (error) {
    console.error('Error committing transaction:', error.response.data);
    throw error;
  }
}

async function voidInflightTransaction(providerReference) {
  const transactionId = await findTransactionId(providerReference);

  try {
    const response = await axios.post(`http://localhost:5001/transactions/${transactionId}/inflight`, {
      status: "void",
      meta_data: {
        verification_status: "REJECTED",
        provider_reference: providerReference
      }
    }, {
      headers: {
        'X-Blnk-Key': 'your-blnk-api-key-here',
        'Content-Type': 'application/json'
      }
    });

    console.log('Transaction voided successfully:', response.data);
    // Here you might want to update your internal database, notify the customer, etc.
  } catch (error) {
    console.error('Error voiding transaction:', error.response.data);
    throw error;
  }
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

This example demonstrates:

  1. Setting up a webhook endpoint to receive notifications from your payment provider.
  2. Processing the webhook based on the payment status (VERIFIED or REJECTED).
  3. Committing or voiding the Inflight transaction accordingly.
  4. Error handling and logging.

Using expiry dates with inflight transactions

Expiration dates are crucial for managing Inflight transactions effectively. They ensure that transactions don’t remain in a pending state indefinitely, which could potentially lock funds unnecessarily.

Example: Setting and handling expiration

NodeJS
const axios = require('axios');

async function createInflightTransactionWithExpiry() {
  const expiryDate = new Date();
  expiryDate.setHours(expiryDate.getHours() + 24); // Set expiry to 24 hours from now

  try {
    const response = await axios.post('http://localhost:5001/transactions', {
      amount: 750.00,
      precision: 100,
      reference: "ref_001adcfgf",
      description: "Payment to ExternalProvider (Pending Verification)",
      currency: "USD",
      source: "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
      destination: "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
      inflight: true,
      inflight_expiry_date: expiryDate.toISOString(),
      meta_data: {
        provider_reference: "ext-pay-123456",
        customer_id: "1234",
        verification_status: "PENDING"
      }
    }, {
      headers: {
        'X-Blnk-Key': 'your-blnk-api-key-here',
        'Content-Type': 'application/json'
      }
    });

    console.log('Inflight transaction created with expiry:', response.data);
    return response.data;
  } catch (error) {
    console.error('Error creating inflight transaction:', error.response.data);
    throw error;
  }
}

createInflightTransactionWithExpiry();

Blnk automatically voids the transaction once the expiration date is met and the transaction has not been committed fully.

Implementing partial commits for inflight transactions

Partial commits allow you to finalize a portion of an Inflight transaction while keeping the remainder in the Inflight state. This is particularly useful for scenarios where a transaction may be processed in parts.

Example: Partial commit of an inflight transaction

NodeJS
async function partialCommitInflightTransaction(transactionId, amountToCommit) {
  try {
    const response = await axios.post(`http://localhost:5001/transactions/${transactionId}/inflight`, {
      status: "commit",
      amount: amountToCommit
    }, {
      headers: {
        'X-Blnk-Key': 'your-blnk-api-key-here',
        'Content-Type': 'application/json'
      }
    });

    console.log('Partial commit successful:', response.data);
    return response.data;
  } catch (error) {
    console.error('Error in partial commit:', error.response.data);
    throw error;
  }
}

// Usage example
async function handlePartialPaymentVerification(transactionId, verifiedAmount) {
  try {
    await partialCommitInflightTransaction(transactionId, verifiedAmount);
    console.log(`Partially committed ${verifiedAmount} for transaction ${transactionId}`);

    // You might want to update your internal records here
    await updateInternalRecords(transactionId, verifiedAmount);

    // Check if there's any remaining amount to be verified
    const remainingAmount = await checkRemainingInflightAmount(transactionId);
    if (remainingAmount > 0) {
      console.log(`Remaining inflight amount for ${transactionId}: ${remainingAmount}`);
    } else {
      console.log(`Transaction ${transactionId} fully committed`);
    }
  } catch (error) {
    console.error('Error handling partial payment verification:', error);
  }
}

async function updateInternalRecords(transactionId, committedAmount) {
  // Implement your logic to update internal records
  console.log(`Updating internal records for ${transactionId} with committed amount ${committedAmount}`);
}

async function checkRemainingInflightAmount(transactionId) {
  // This would typically involve querying Blnk's API or your database
  // For demonstration, we'll return a mock value
  return 250.00; // Assuming 500 out of 750 was committed
}

// Example usage
handlePartialPaymentVerification('txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d', 500.00);

In this example:

  1. We defined a partialCommitInflightTransaction function that commits only a portion of the Inflight transaction.
  2. The handlePartialPaymentVerification function demonstrates how you might use partial commits in your payment flow:
    • It commits the verified amount.
    • Updates internal records.
    • Checks if there’s any remaining amount still in the Inflight state.
  3. The checkRemainingInflightAmount function (which you would implement to query Blnk’s API or your database) helps track the progress of partial commits.

By using partial commits, you can handle complex scenarios where funds are released incrementally, such as:

  • Multi-stage payment processes.
  • Partial fulfillment of orders.
  • Gradual release of funds based on milestone completions in a project.

Best practices for managing side effects with Inflight

  1. Balance Checks: Ensure the source balance has enough funds to complete the transaction. Prevent the source balance from having a balance lower than the amount in its inflight_debit_balance.

  2. Available Balance Calculation: This prevents users from accessing funds that are held in Inflight transactions. In your application, calculate the available balance as:

    const availableBalance = balance - inflight_debit_balance;
    
  3. Error Handling: Implement robust error handling in your Inflight process. If a commit or void operation fails, you may need to retry or escalate to manual intervention.

  4. Customer Communication: Implement a system to notify customers about the status of their transactions, especially when they’re held in Inflight.

  5. Reconciliation: Regularly reconcile your internal records with Blnk’s transaction logs to ensure accuracy, paying special attention to Inflight transactions.(Blnk v1 will support reconcillation features built into the ledger).

  6. Webhook Authentication: In a production environment, implement a mechanism to verify that the webhook is genuinely from your payment provider.

  7. Idempotency: Ensure your webhook handler is idempotent. Providers may send the same webhook multiple times, so your system should handle duplicate notifications gracefully.

  8. Monitoring: Set up monitoring and alerting for your webhook endpoint and Inflight transactions. This can help you quickly identify and respond to any issues in the payment verification process.

Need help?

Are you stuck? Do you have a question that isn’t answered in this doc? Have you run into a problem you can’t solve? Want to file a bug report?

Join our Discord server and share your questions/thoughts with other developers building financial applications like you.

Was this page helpful?