Bulk payments

The flow of bulk payments is the same as for normal payment:

  • Initialization
  • redirect to bank
  • finalization
  • status

Note that, for some banks, the bulk is uploaded to the bank, the bank take some time to process it and it must be approved in the bank's dashboard. See with you bank how it works.

Note also that Belfius is doing checks asynchronously at creation step and it can take a while before you can redirect to the bank.

For some bank, like BNP BE, the SCA may take some time (up to several minutes) while they are validating the payment file.

Options

Note that the JSON has been simplified by removing non relevant structure and taking only the SEPA. Here you can see that:

  • bulk payment are enable: SepaCreditTransfers.BulkPayments.Supported is true;
  • You can submit one bulk at a time: SepaCreditTransfers.BulkPayments.MaxNumberOfBulk;
  • You may specify if the payments are treated separately on in batch SepaCreditTransfers.BulkPayments.BatchBooking is optional(1). It could also be Required(0) or unused(2).
    Note that the final processing depends on the bank;
  • You can submit 1000 payments by bulk in BATCH(1) mode and 1000 in INDIVIDUAL(0) mode;
  • You cannot use private account, only business: PrivateAccountsSupported is false;
  • PaymentInitiationRequestOptions is the same as for single payment;
{
  "DomesticTransfers": {},
  "InstantDomesticTransfers": {},
  "SepaCreditTransfers": {
    "SinglePayments": {},
    "PeriodicPayments": {},
    "BulkPayments": {
      "ExecutionTypeOptions": [
        {
          "MaxNumberOfPayments": 1000,
          "ExecutionType": 1
        },
        {
          "MaxNumberOfPayments": 1000,
          "ExecutionType": 0
        }
      ],
      "MaxNumberOfBulk": 1,
      "BatchBooking": 1,
      "PrivateAccountsSupported": false,
      "PaymentInitiationRequestOptions": {
        "Recipient": {},
        "Debtor": {},
        "EndToEndId": 0,
        "RequestedExecutionDate": 1,
        "RegulatoryReportingCodeRequired": 2,
        "RegulatoryReportingCodes": [],
        "RemittanceInformationUnstructured": 0,
        "RemittanceInformationUnstructuredConstrains": {
          "MinLength": 1,
          "MaxLength": 140,
          "AcceptedChars": "a-zA-Z0-9?:'\\/\\()+, \\-\\.áâäàåãÄÅÁÂÀÃÇçêëèéÊËÈÉïîìíÍÎÏÌôöòõóÓÖÔÒÕûùúüÚÛÙÜñÑýÝÿ",
          "Regex": "^[a-zA-Z0-9?:'\\/\\()+, \\-\\.áâäàåãÄÅÁÂÀÃÇçêëèéÊËÈÉïîìíÍÎÏÌôöòõóÓÖÔÒÕûùúüÚÛÙÜñÑýÝÿ]{1,140}$"
        }
      },
      "Supported": true,
      "CancelSupported": false,
      "SpecificPaymentDate": 1,
      "PsuInformation": null
    },
    "PaymentInitiationRequestOptions": {},
    "AdditionalPropertiesRequested": [],
    "AccountsList": 0
  },
  "InstantSepaCreditTransfers": {
    "SinglePayments": {},
    "PeriodicPayments": {},
    "BulkPayments": {},
    "PaymentInitiationRequestOptions": {},
    "AdditionalPropertiesRequested": [],
    "AccountsList": 0
  },
  "CrossborderPayments": {},
  "Target2Payment": {},
  "AdditionalPropertiesRequested": []
}

Here is an example with BNP.

Initialization

POSTing to {{baseUrl}}/ob/Pis/payments/bulk

{
    "connectorId": 2,
    "userContext": {{userContext}},
    "bulkPaymentInitiationRequest": {
        "Bulks": [
            {
                "BulkId": "56b69792f...ff87af938d",
                "BatchBooking": false,
                "Payments": [
                    {
                        "PaymentId": "7c15eb...bab00e64f",
                        "Recipient": {
                            "Name": "Exthand SA",
                            "Iban": "BE680...934",
                            "Currency": "EUR"
                        },
                        "Amount": 1.2,
                        "Currency": "EUR",
                        "EndToEndId": "851c0...1f3d1fa86",
                        "RemittanceInformationUnstructured": "test bankingsdk"
                    },
                    {
                        "PaymentId": "c5c2ed...ea4f08119b",
                        "Recipient": {
                            "Name": "Exthand SA",
                            "Iban": "BE26...829",
                            "Currency": "EUR"
                        },
                        "Amount": 1.4,
                        "Currency": "EUR",
                        "EndToEndId": "b5f6...2e8f8bd",
                        "RemittanceInformationUnstructured": "test bankingsdk"
                    }
                ],
                "Debtor": {
                    "Name": "John Doe LTD",
                    "Iban": "BE760...395",
                    "Currency": "EUR"
                },
                "PaymentPriority": 0,
                "PaymentProduct": 1,
                "ExecutionType": 0,
                "RequestedExecutionDate": "2023-11-23T09:53:04.365192+01:00",
                "AdditionalProperties": {}
            }
        ],
        "RedirectUrl": "https://developer.bankingsdk.com/callback",
        "FlowId": "a7b1220...1-e3aa52c5a8be",
        "BulksId": "96a790...ada8a4b787",
        "PsuIp": "81.241.198.176",
        "PsuAcceptLanguage": "fr-FR,fr;q=0.9"
    },
    "tppContext": {
        "flow": "test bulk",
        "transaction": "{{flowId}}"
    }
}

The top fields are the same as for payments, the connector id, the user context and, optionally, the tppContext.

We structured the bulkPaymentInitiationRequest as capable of handling multiple bulks and each bulk containing multiple payments. To know how much bulks you can put in one call, check the payment options of the connector.

The payments are also the same as the single payment except that the debtor is on level up to be common for all the payments of the bulk.

If the initialization call returns a response status DONE(1), you have to call the finalize with null dataString while you get RETRY (8). If you get REDIRECT(2), the bank is ready and supplied the SCA redirection link, you can redirect the user to that SCA link. When back from the bank, you will call finalize again with the query string sent by the bank like in single payment.

Finalization

Back from the bank, you have to proceed like for single payment, find the flow id, restore connector id, the flow context and user context from your storage and call the finalize end point with the query string received from the bank.

PUTting to {{baseUrl}}/ob/Pis/payments/bulk

{
  "flow": {{flowContext}},
  "dataString": "?flowId=ed15395f-8c...49fe&original=%3fflowId%3d9058f9f4-...fd2c64e",
    "userContext": {{userContext}},
  "tppContext": {
    "flow": "test bulk",
    "transaction": "{{flowId}}"
  }
}

Be prepared to handle the different response status, DONE, RETRY, REDIRECT...

The response:

{
  "PaymentStatus": {
    "Status": 3,
    "StatusCodeRaw": "ACSP",
    "BulksId": "96a790a36...ada8a4b787",
    "BankReferenceId": "61eae4...4dca8",
    "BankEndToEndId": null,
    "Bulks": [
      {
        "BulkId": "5f6ca0ac...2a799c93a0",
        "Payments": [
          {
            "EndToEndIdentification": "5cddb...2d01a35fbe8aa",
            "PaymentId": "f4f96c...bd66ed6acfb0e",
            "InternalBankReference": null,
            "BankReferenceId": null,
            "Status": 10,
            "StatusCodeRaw": null
          },
          {
            "EndToEndIdentification": "625343a...5602cd46a17d0",
            "PaymentId": "478bd92...5777f6086d3312",
            "InternalBankReference": null,
            "BankReferenceId": null,
            "Status": 10,
            "StatusCodeRaw": null
          }
        ],
        "Status": 10,
        "StatusCodeRaw": null
      }
    ]
  },
  "Message": null,
  "Options": null
}

The completion of the response depends on the bank. The more information we receive from the bank the more data we are returning. In the illustration, you see the Bulk set, the bulks and the payments. If the bank has an individual payment status by payment, you will get it. In this case, it's unknown(10) but the whole bulk has ACSP(3).

BankReferenceId is given by the bank to identify the "payment file“.

BankEndToEndId may be supplied by the bank.

We would advise to save all references you may get from the bank for later exchange with the bank. In our case, you don't need any.

Status

When the finalize is DONE, you can call get status

POSTing to {{baseUrl}}/ob/Pis/payments/bulk/status

{
    "connectorId": 2,
    "userContext": {{userContext}},
    "tppContext": {
        "flow": "test michael",
        "transaction": "{{flowId}}"
    },
    "flow": {{flowContext}}
} 

The response is the same structure as finalize's "PaymentStatus" field:

{
    "Status": 3,
    "StatusCodeRaw": "ACSP",
    "BulksId": "96a790a36...ada8a4b787",
    "BankReferenceId": "61eae4...4dca8",
    "BankEndToEndId": null,
    "Bulks": [
      {
        "BulkId": "5f6ca0ac...2a799c93a0",
        "Payments": [
          {
            "EndToEndIdentification": "5cddb...2d01a35fbe8aa",
            "PaymentId": "f4f96c...bd66ed6acfb0e",
            "InternalBankReference": null,
            "BankReferenceId": null,
            "Status": 10,
            "StatusCodeRaw": null
          },
          {
            "EndToEndIdentification": "625343a...5602cd46a17d0",
            "PaymentId": "478bd92...5777f6086d3312",
            "InternalBankReference": null,
            "BankReferenceId": null,
            "Status": 10,
            "StatusCodeRaw": null
          }
        ],
        "Status": 10,
        "StatusCodeRaw": null
      }
    ]
  }