Skip to main content

Engineering Notes

Integrating the BillDesk payment gateway with PHP

In 2014, E Multitech developer Binita Thakur solved a difficult BillDesk PHP integration and shared the working reasoning so other developers could benefit. This historical retrospective preserves that problem-solving record without presenting the legacy interface as current guidance.

By Binita Thakur Published Substantially revised 7 min read

Historical implementation — not current production guidance. This article records an integration completed in 2014. BillDesk APIs, endpoints, message fields, checksum methods and security requirements may have changed. Before implementing anything in production, obtain the current merchant documentation through the official BillDesk website or your provider contact and have the proposed integration professionally reviewed. Every value below is an unmistakable placeholder or an environment-variable name.

Why the solution was shared

The original post came from a practical engineering problem, not a marketing exercise. After working through a difficult BillDesk integration, an E Multitech developer documented the approach so another PHP developer facing the same opaque message flow would have a clearer starting point. That act of sharing is the history worth retaining.

The useful part was the reasoning: understand the positional request, keep the signing step in one place, distrust a response carried through the browser, and verify it on the server before changing payment state.

The 2014 integration model

In the interface documented in 2014, the merchant application assembled a positional, pipe-delimited transaction message. It added the identifiers and return location issued for that merchant, calculated an HMAC-SHA256 digest with a shared checksum key, and appended the digest to the message. A response later returned through the customer’s browser and had to be verified by the merchant server.

Those details are historical. They describe what the engineer had to solve at the time; they do not assert that BillDesk uses the same fields, endpoints, algorithm, callback transport or status values today.

Illustrative legacy request pattern

The following code is an illustrative legacy pattern, not production-ready integration code. It deliberately contains no live endpoint, merchant value, client reference or secret. Secrets are read only from environment variables; a real deployment should inject them from protected secret storage.

$merchantId     = getenv('BILLDESK_MERCHANT_ID');
$securityId     = getenv('BILLDESK_SECURITY_ID');
$checksumKey    = getenv('BILLDESK_CHECKSUM_KEY');
$gatewayEndpoint = getenv('BILLDESK_GATEWAY_ENDPOINT');
$returnUrl      = getenv('BILLDESK_RETURN_URL');

// Historical positional shape, shortened for illustration.
$legacyFields = [
    $merchantId,
    '[YOUR_ORDER_REFERENCE]',
    '[FIELD_UNUSED_IN_LEGACY_PATTERN]',
    '[YOUR_TRANSACTION_AMOUNT]',
    '[YOUR_CURRENCY_CODE]',
    $securityId,
    $returnUrl,
];

$legacyBody = implode('|', $legacyFields);

// HMAC-SHA256 reflects the 2014 implementation only.
$legacyChecksum = strtoupper(
    hash_hmac('sha256', $legacyBody, $checksumKey, false)
);
$legacyMessage = $legacyBody . '|' . $legacyChecksum;

// Use $gatewayEndpoint only as directed by current provider documentation.

The important historical insight is that positional formats are unforgiving. A missing separator changes the meaning of every field after it, so the message should be assembled from a clearly named structure in one audited server-side component. The checksum provides integrity, not confidentiality: transport security and careful data handling remain separate requirements.

The durable response-verification lesson

The 2014 solution did not trust the payment result merely because a browser posted it back. It separated the received digest from the message, recomputed the digest on the server and compared the two before evaluating the documented transaction status.

$legacyPayload = $_POST['msg'] ?? '';
$lastSeparator = strrpos($legacyPayload, '|');

if ($lastSeparator === false) {
    throw new RuntimeException('Invalid legacy response shape');
}

$legacyBody = substr($legacyPayload, 0, $lastSeparator);
$receivedChecksum = substr($legacyPayload, $lastSeparator + 1);
$expectedChecksum = strtoupper(
    hash_hmac('sha256', $legacyBody, $checksumKey, false)
);

if (!hash_equals($expectedChecksum, $receivedChecksum)) {
    throw new RuntimeException('Response integrity check failed');
}

// Read the status field and success value from current provider documentation.
// Do not fulfil an order from browser-visible data alone.

This remains a useful security principle, but the sample is not a specification. A current implementation must follow the provider’s present verification, reconciliation, replay-protection and failure-handling requirements.

Security note

Live credentials must never appear in source code, repositories, logs, screenshots, tickets or articles. Do not log an entire signed request or response. Store secrets in a protected secret manager or equivalent platform facility, inject them into the server process, restrict access, and rotate or revoke them through an authorised process when exposure is suspected. The historical credential incident remains documented separately in the private migration security record; no credential or partial value is reproduced here.

What we would do differently today

  • Start with current documentation. Obtain the latest merchant specification, endpoint information, supported signing method and test credentials directly from BillDesk before writing production code.
  • Protect every secret. Keep credentials in managed secret storage and expose them to the smallest possible server-side runtime scope through environment injection or an equivalent protected mechanism.
  • Verify on the server. Validate authenticity and integrity before changing payment state, then reconcile against the provider’s authoritative server-side records using the current supported process.
  • Log safely. Record correlation identifiers, state transitions and redacted error categories rather than credentials, signatures, full payloads or sensitive customer data.
  • Test failure paths. Exercise tampered messages, amount or currency mismatches, duplicate delivery, replay attempts, timeouts, partial failures and reconciliation differences in an isolated test environment.
  • Require professional review. Have the provider and qualified payment/security engineers review the final design, operational controls and launch checklist. Our current banking and fintech software engineering work treats this review as part of the transaction boundary.

The durable engineering lesson

The interface may have changed, but the problem-solving record remains valuable. E Multitech’s engineer turned a difficult integration into a reproducible explanation for other developers: centralise message construction, keep secrets out of public artefacts, verify payment evidence on the server, and treat browser-carried state as untrusted. That is the historical contribution this retrospective preserves.