PHPackages                             currence/emandates - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. currence/emandates

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

currence/emandates
==================

Supporting libraries for eMandates protocol

2.0.0(3mo ago)5335↓91.4%3[1 issues](https://github.com/Betaalvereniging-Nederland/emandates-libraries-php/issues)[2 PRs](https://github.com/Betaalvereniging-Nederland/emandates-libraries-php/pulls)MITPHPPHP &gt;=8.1

Since Jun 12Pushed 3mo ago3 watchersCompare

[ Source](https://github.com/Betaalvereniging-Nederland/emandates-libraries-php)[ Packagist](https://packagist.org/packages/currence/emandates)[ RSS](/packages/currence-emandates/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (5)Dependencies (2)Versions (7)Used By (0)

eMandates PHP Library
=====================

[](#emandates-php-library)

PHP library for Dutch electronic SEPA direct debit mandates (eMandates) using the iDx protocol. It provides a complete implementation for issuing, amending, cancelling, and checking the status of eMandates through the Currence eMandates scheme.

Requirements
------------

[](#requirements)

- **PHP &gt;= 8.1** (compatible through 8.4)
- PHP extensions:
    - `ext-dom`
    - `ext-simplexml`
    - `ext-curl`
    - `ext-openssl`

Installation
------------

[](#installation)

```
composer require currence/emandates
```

Then include the Composer autoloader in your application:

```
require __DIR__ . '/vendor/autoload.php';
```

Configuration
-------------

[](#configuration)

The library needs credentials, certificate paths, and acquirer URLs before it can communicate with the eMandates platform. There are three ways to provide configuration.

### Option 1: Global config array with `Configuration::getDefault()`

[](#option-1-global-config-array-with-configurationgetdefault)

Create a file (e.g. `eMandatesConfig.php`) that populates a global array:

```
global $emandates_config_params;

$emandates_config_params = [
    'passphrase'                   => 'your-private-key-passphrase',
    'keyFile'                      => '/path/to/merchant.key',
    'crtFile'                      => '/path/to/merchant.crt',
    'crtFileAquirer'               => '/path/to/acquirer.crt',
    'crtFileAquirerAlternative'    => '/path/to/acquirer-alt.crt',
    'contractID'                   => '0000000000',
    'contractSubID'                => '0',
    'merchantReturnURL'            => 'https://example.com/return',
    'AcquirerUrl_DirectoryReq'     => 'https://acquirer.example.com/directory',
    'AcquirerUrl_TransactionReq'   => 'https://acquirer.example.com/transaction',
    'AcquirerUrl_StatusReq'        => 'https://acquirer.example.com/status',
    'enableXMLLogs'                => true,
    'logPath'                      => '/path/to/logs',
    'folderNamePattern'            => 'Y-m-d',
    'fileNamePrefix'               => 'emandates',
    'enableInternalLogs'           => true,
    'fileName'                     => 'emandates.log',
];
```

Then obtain a `Configuration` object:

```
use EMandates\Merchant\Configuration\Configuration;

require_once 'eMandatesConfig.php';

$config = Configuration::getDefault();
```

### Option 2: Constructor

[](#option-2-constructor)

Build a `Configuration` directly:

```
use EMandates\Merchant\Configuration\Configuration;

$config = new Configuration(
    passphrase: 'your-private-key-passphrase',
    keyFile: '/path/to/merchant.key',
    crtFile: '/path/to/merchant.crt',
    crtFileAquirer: '/path/to/acquirer.crt',
    crtFileAquirerAlternative: '/path/to/acquirer-alt.crt',
    contractID: '0000000000',
    contractSubID: '0',
    merchantReturnURL: 'https://example.com/return',
    AcquirerUrl_DirectoryReq: 'https://acquirer.example.com/directory',
    AcquirerUrl_TransactionReq: 'https://acquirer.example.com/transaction',
    AcquirerUrl_StatusReq: 'https://acquirer.example.com/status',
    enableXMLLogs: true,
    logPath: '/path/to/logs',
    folderNamePattern: 'Y-m-d',
    fileNamePrefix: 'emandates',
    enableInternalLogs: true,
    fileName: 'emandates.log',
);
```

### Option 3: XML config file with `Configuration::load()`

[](#option-3-xml-config-file-with-configurationload)

If you store settings in an XML file:

```

```

Load it:

```
$config = Configuration::load('/path/to/config.xml');
```

Usage Examples
--------------

[](#usage-examples)

### Creating a Communicator

[](#creating-a-communicator)

`CoreCommunicator` handles SEPA Core mandates. `B2BCommunicator` extends it for SEPA B2B mandates and adds cancellation support.

```
use EMandates\Merchant\Communicator\CoreCommunicator;
use EMandates\Merchant\Communicator\B2BCommunicator;
use EMandates\Merchant\Configuration\Configuration;

// Using explicit configuration
$config = Configuration::getDefault();
$coreCommunicator = new CoreCommunicator($config);
$b2bCommunicator  = new B2BCommunicator($config);

// Or rely on the default (reads from global $emandates_config_params)
$coreCommunicator = new CoreCommunicator();
$b2bCommunicator  = new B2BCommunicator();
```

### 1. Directory Request -- Get List of Debtor Banks

[](#1-directory-request----get-list-of-debtor-banks)

```
$directoryResponse = $coreCommunicator->Directory();

if ($directoryResponse->IsError) {
    echo 'Error: ' . $directoryResponse->Error->ErrorMessage;
} else {
    foreach ($directoryResponse->DebtorBanks as $bank) {
        echo $bank->DebtorBankId . ' - ' . $bank->DebtorBankName
             . ' (' . $bank->DebtorBankCountry . ')' . PHP_EOL;
    }
}
```

### 2. New Mandate -- Issue a New eMandate

[](#2-new-mandate----issue-a-new-emandate)

```
use EMandates\Merchant\Request\NewMandateRequest;

$request = new NewMandateRequest(
    entranceCode:     'unique-entrance-code-123',
    language:         'en',
    messageId:        '',                         // leave empty to auto-generate
    eMandateId:       'MANDATE-2025-001',
    eMandateReason:   'Monthly subscription',
    debtorReference:  'CUST-42',
    debtorBankId:     'INGBNL2A',
    purchaseId:       'ORDER-789',
    sequenceType:     'RCUR',                     // 'RCUR' (recurring) or 'OOFF' (one-off)
    maxAmount:        '',                         // optional, B2B only
    expirationPeriod: new \DateInterval('PT30M'), // optional, e.g. 30 minutes
);

$response = $coreCommunicator->NewMandate($request);

if ($response->IsError) {
    echo 'Error: ' . $response->Error->ErrorMessage;
} else {
    // Save the transaction ID for the status request later
    $transactionId = $response->TransactionId;

    // Redirect the debtor to their bank
    if ($response->IssuerAuthenticationUrl) {
        header('Location: ' . $response->IssuerAuthenticationUrl);
        exit;
    }
}
```

### 3. Amendment -- Amend an Existing Mandate

[](#3-amendment----amend-an-existing-mandate)

```
use EMandates\Merchant\Request\AmendmentRequest;

$amendRequest = new AmendmentRequest(
    entranceCode:         'unique-entrance-code-456',
    language:             'en',
    eMandateId:           'MANDATE-2025-001',
    eMandateReason:       'Updated subscription terms',
    debtorReference:      'CUST-42',
    debtorBankId:         'INGBNL2A',
    purchaseId:           'ORDER-789',
    sequenceType:         'RCUR',
    originalIBAN:         'NL91ABNA0417164300',
    originalDebtorBankId: 'ABNANL2A',
    messageId:            '',                         // optional, auto-generated if empty
    expirationPeriod:     new \DateInterval('PT20M'),  // optional
);

$amendResponse = $coreCommunicator->Amend($amendRequest);

if ($amendResponse->IsError) {
    echo 'Error: ' . $amendResponse->Error->ErrorMessage;
} else {
    $transactionId = $amendResponse->TransactionId;
    if ($amendResponse->IssuerAuthenticationUrl) {
        header('Location: ' . $amendResponse->IssuerAuthenticationUrl);
        exit;
    }
}
```

### 4. Cancellation -- Cancel a Mandate (B2B only)

[](#4-cancellation----cancel-a-mandate-b2b-only)

Cancellation is only available through `B2BCommunicator`.

```
use EMandates\Merchant\Request\CancellationRequest;

$cancelRequest = new CancellationRequest(
    entranceCode:     'unique-entrance-code-789',
    language:         'en',
    eMandateId:       'MANDATE-2025-001',
    eMandateReason:   'Contract terminated',
    debtorReference:  'CUST-42',
    debtorBankId:     'INGBNL2A',
    purchaseId:       'ORDER-789',
    sequenceType:     'RCUR',
    originalIBAN:     'NL91ABNA0417164300',
    messageId:        '',                         // optional
    maxAmount:        '1000.00',                  // optional, B2B only
    expirationPeriod: new \DateInterval('PT20M'),  // optional
);

$cancelResponse = $b2bCommunicator->Cancel($cancelRequest);

if ($cancelResponse->IsError) {
    echo 'Error: ' . $cancelResponse->Error->ErrorMessage;
} else {
    $transactionId = $cancelResponse->TransactionId;
    if ($cancelResponse->IssuerAuthenticationUrl) {
        header('Location: ' . $cancelResponse->IssuerAuthenticationUrl);
        exit;
    }
}
```

### 5. Get Status -- Check Transaction Status

[](#5-get-status----check-transaction-status)

After the debtor returns from the bank, check the transaction outcome.

```
use EMandates\Merchant\Request\StatusRequest;
use EMandates\Merchant\Enum\TransactionStatus;

$statusRequest = new StatusRequest(TransactionId: $transactionId);
$statusResponse = $coreCommunicator->GetStatus($statusRequest);

if ($statusResponse->IsError) {
    echo 'Error: ' . $statusResponse->Error->ErrorMessage;
} else {
    // $statusResponse->Status is a TransactionStatus enum instance
    switch ($statusResponse->Status) {
        case TransactionStatus::Success:
            $report = $statusResponse->AcceptanceReport;
            echo 'Mandate accepted!' . PHP_EOL;
            echo 'Debtor IBAN: ' . $report->DebtorIBAN . PHP_EOL;
            echo 'Debtor name: ' . $report->DebtorAccountName . PHP_EOL;
            echo 'Debtor bank: ' . $report->DebtorBankId . PHP_EOL;
            break;

        case TransactionStatus::Open:
        case TransactionStatus::Pending:
            echo 'Transaction is still in progress.' . PHP_EOL;
            break;

        case TransactionStatus::Cancelled:
            echo 'Transaction was cancelled by the debtor.' . PHP_EOL;
            break;

        case TransactionStatus::Expired:
            echo 'Transaction has expired.' . PHP_EOL;
            break;

        case TransactionStatus::Failure:
            echo 'Transaction failed.' . PHP_EOL;
            break;
    }

    // If you need the raw string value:
    echo 'Status string: ' . $statusResponse->Status->value . PHP_EOL;
}
```

Custom Logger
-------------

[](#custom-logger)

Implement `LoggerInterface` to capture log output (e.g. to a database):

```
use EMandates\Merchant\Contract\LoggerInterface;

class DBLogger implements LoggerInterface
{
    public function Log(string $message): void
    {
        // Store $message in your database or logging system
    }

    public function LogXmlMessage(\DOMDocument|string $dom, bool $isXML = false, string $fileName = ''): void
    {
        // Store XML request/response messages
        $xml = $dom instanceof \DOMDocument ? $dom->saveXML() : $dom;
        // ... persist $xml
    }
}
```

Pass the logger when constructing a communicator:

```
$logger = new DBLogger();
$communicator = new CoreCommunicator(Configuration::getDefault(), $logger);
```

Error Handling
--------------

[](#error-handling)

All communicator methods (`Directory`, `NewMandate`, `Amend`, `Cancel`, `GetStatus`) return response objects that include error information rather than throwing exceptions to the caller. This includes acquirer errors, unexpected response formats, missing XML elements, and non-XML error messages.

```
$response = $coreCommunicator->NewMandate($request);

if ($response->IsError) {
    $error = $response->Error;

    echo 'Error code: '      . $error->ErrorCode      . PHP_EOL;
    echo 'Error message: '   . $error->ErrorMessage    . PHP_EOL;
    echo 'Error details: '   . $error->ErrorDetails    . PHP_EOL;
    echo 'Suggested action: '. $error->SuggestedAction  . PHP_EOL;
    echo 'Consumer message: '. $error->ConsumerMessage   . PHP_EOL;
}
```

The `ErrorResponse` object provides these properties:

PropertyDescription`ErrorCode`Machine-readable error code from the acquirer`ErrorMessage`Human-readable error description`ErrorDetails`Additional detail about the error`SuggestedAction`Recommended corrective action`ConsumerMessage`Message suitable for display to the end user**Note:** Transaction response objects (`NewMandateResponse`, `AmendmentResponse`, `CancellationResponse`) also accept `AcquirerStatusRes` XML. In this case the response will not have an `IssuerAuthenticationUrl`, so always use null-safe access:

```
if ($response->IssuerAuthenticationUrl) {
    header('Location: ' . $response->IssuerAuthenticationUrl);
    exit;
}
```

If a communication-level error occurs (e.g. invalid XML response, schema validation failure), a `CommunicatorException` may be thrown internally. This exception provides `errorCode` and `errorMessage` as readonly properties:

```
use EMandates\Merchant\Exception\CommunicatorException;

try {
    $response = $coreCommunicator->NewMandate($request);
} catch (CommunicatorException $e) {
    echo $e->errorCode;
    echo $e->errorMessage;
}
```

Upgrading from the Legacy Version
---------------------------------

[](#upgrading-from-the-legacy-version)

If you are upgrading from the older PHP 5.5+ version of this library, see [UPGRADE.md](UPGRADE.md) for a complete list of breaking changes and migration steps.

License
-------

[](#license)

This library is released under the [MIT License](LICENSE).

Copyright (c) 2018 Currence-Online

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance78

Regular maintenance activity

Popularity20

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity76

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~947 days

Total

4

Last Release

92d ago

Major Versions

1.16.7.x-dev → 2.0.0.x-dev2026-04-03

### Community

Maintainers

![](https://www.gravatar.com/avatar/3a0edaaad0542c99bb103d6139d10a75e7cfc55cc2264cf550607d0ec9948af1?d=identicon)[maxcode-devops-blue](/maintainers/maxcode-devops-blue)

---

Top Contributors

[![maxcode-devops-blue](https://avatars.githubusercontent.com/u/65350856?v=4)](https://github.com/maxcode-devops-blue "maxcode-devops-blue (6 commits)")[![vladiliescu](https://avatars.githubusercontent.com/u/331671?v=4)](https://github.com/vladiliescu "vladiliescu (5 commits)")[![grigoresc](https://avatars.githubusercontent.com/u/2048798?v=4)](https://github.com/grigoresc "grigoresc (2 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/currence-emandates/health.svg)

```
[![Health](https://phpackages.com/badges/currence-emandates/health.svg)](https://phpackages.com/packages/currence-emandates)
```

###  Alternatives

[salla/zatca

A helper to generate the QR code and signed it for ZATCA e-invoicing

164449.9k2](/packages/salla-zatca)[litesaml/lightsaml

SAML 2.0 PHP library

1076.3M24](/packages/litesaml-lightsaml)[egroupware/egroupware

EGroupware extends a classic groupware with an integrated CRM-system, a secure file-server and Collabora Online Office.

2931.7k](/packages/egroupware-egroupware)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
