Invoice Batching

The details below describes the invoice batch API and provides information on how integration partners can leverage this capability to streamline the creation of large numbers of on-demand payments without the need to execute hundreds (or thousands) of synchronous requests against the on-demand invoice API.

Introduction

Invoice batching provides integration partners with the ability to submit large batches of on-demand invoice objects for asynchronous processing - this processing model provides the same capabilities as on-demand invoicing and supports tokenised credit cards, debit cards, and direct debit payment methods. This section describes the invoice batch request body structure, how to submit and monitor invoice batches, and the semantics associated with handling webhook events, errors, and retries.Requests to create batches of on-demand invoices may contain up to 5000 distinct invoice objects, each of these objects should adhere to the structure and content mandated by the on-demand invoice API. Processing time for submitted batches can vary depending on system load and the number of invoice objects included in the invoice batch request, however most batches will be processed within a few hours of submission. An invoice batch is considered to have been processed when all encapsulated invoice objects have either been used to create an on-demand invoice in v2 or have resulted in an unrecoverable error. It is important to note that even though an invoice batch has transitioned to a successfully processed state, the resulting on-demand invoices will remain in a processing state until all associated payments have been processed.

🚧

Important!

On-demand invoices are separate and not associated with any customer subscriptions. If you want to make changes to the auto-generated invoices issued from a subscription during each billing cycle, see update a future invoice.

Prerequisites

  • The merchant's billing must be turned on (which can be found under the merchant settings). If a merchant's auto payment is disabled, then any on-demand invoices created will not generate a payment transaction. The invoice status will display as past due. Contact Ezypay Support if merchant billing is off.
  • On-demand invoices can only be issued to existing customers. If you don't already have the Customer ID, you must create a customer first before creating an invoice.
  • On-demand invoices can only select payment methods from existing payment method tokens. If you don't already have the payment method token ID, you must create a payment method token first if you want to include a payment method with the invoice. You can also create an on-demand invoice without a payment method, however, these invoices will have status past due and no payment transactions will occur.

External invoice identifiers

It is possible for integration partners to provide an optional unique identifier (of up to 250 characters in length) for each on-demand invoice object encapsulated by a batch. This identifier should be provided as the value of the externalInvoiceId property within each on-demand invoice object and must be globally unique to the merchant associated with the invoice batch creation request. These external identifiers are intended to enable integration partners to handle the semantics of asynchronous on-demand invoice creation by providing a mechanism to support correlation between webhook events emitted by v2 and extant invoice and/or payment entities within partner systems. The value of externalInvoiceId is generally the unique identifier assigned by integrators when creating a payment or invoice within their own system. Requests to create on-demand invoices including a externalInvoiceId value that is not unique in the context of the current merchant will result in a validation error - a unique constraint exists within v2 between the merchant identifier and the external invoice identifier. This constraint is not enforced when the externalInvoiceId property is omitted from on-demand invoice objects or contains a null value. Integration partners may resolve invoices within v2 using external invoice identifiers via the list invoice API. An example request is included below to demonstrate this request/response cycle:

GET /v2/billing/invoices?externalInvoiceId=INV3-Q8TP-5IME-AAKU-NG74
Content-Type: application/json
Cache-Control: no-cache
merchant: {your merchant identifier}
cache-control: no-cache
Authorization: Bearer {your JWT}
User-Agent: Integration Partner v1.0
Accept: */*
Host: api-global.ezypay.com
accept-encoding: gzip, deflate

Below is an example of the response produced by the above request:

HTTP/1.1 200
status: 200
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Tue, 05 Mar 2019 09:52:13 GMT
Expires: 0
Pragma: no-cache
Content-Length: 2250
Connection: keep-alive

{
    "data": [
        {
            "id": "7478bf57-7a8c-4f8a-af79-af36b70cc89a",
            "documentNumber": "IN0000000000024120",
            "date": "2019-03-05",
            "dueDate": "2019-03-08",
            "scheduledPaymentDate": null,
            "status": "PROCESSING",
            "memo": "payment for widget",
            "externalInvoiceId": "INV3-Q8TP-5IME-AAKU-NG74",
            "items": [
                {
                    "description": "payment for widget",
                    "amount": {
                        "currency": "AUD",
                        "value": 25.5,
                        "type": null
                    },
                    "tax": {
                        "rate": 10
                    },
                    "id": "4d592d81-4e73-457c-87c1-0e2bf52dc408",
                    "type": "on_demand_payment",
                    "discounted": {
                        "currency": "AUD",
                        "value": 0,
                        "type": null
                    },
                    "accountingCode": "widget"
                }
            ],
            "amount": {
                "currency": "AUD",
                "value": 25.5,
                "type": null
            },
            "amountWithoutDiscount": {
                "currency": "AUD",
                "value": 25.5,
                "type": null
            },
            "totalDiscounted": {
                "currency": "AUD",
                "value": 0,
                "type": null
            },
            "totalRefunded": {
                "currency": "AUD",
                "value": 0,
                "type": null
            },
            "totalTax": {
                "currency": "AUD",
                "value": 2.32,
                "type": null
            },
            "customerId": "110f62d2-71e7-44d8-a9aa-e81588a12f3d",
            "subscriptionId": null,
            "subscriptionName": null,
            "paymentMethodToken": "fa46d13a-022c-45fd-84b1-4ba8870490d5",
            "autoPayment": true,
            "createdOn": "2019-03-05T09:45:41.740"
        }
    ],
    "paging": {
        "nextUrl": "https://api-global.ezypay.com/v2/billing/invoices?limit=1&cursor=1",
        "nextCursor": 1,
        "limit": 1,
        "totalCount": 1
    }
}

Creating an invoice batch

Integration partners are able to submit requests to create up to 5000 on-demand invoices via a POST request to the invoice batch API. An example request to create an invoice batch is shown below:

POST /v2/billing/invoices/batches
Content-Type: application/json
Cache-Control: no-cache
merchant: {your merchant identifier}
cache-control: no-cache
Authorization: Bearer {your JWT}
User-Agent: Integration Partner v1.0
Accept: */*
Host: api-global.ezypay.com
accept-encoding: gzip, deflate
content-length: 1106

{ 
   "batchReference":"BATCH-REF-00000123",
   "invoices":[ 
      { 
         "customerId":"0cc14fb3-3be5-451f-a63d-9c19ae9627ab",
         "paymentMethodToken":"2cf436b1-bb9a-4934-b626-805830d7a939",
         "externalInvoiceId": "INV2-000101022",
         "memo": "this is a test invoice",
         "items":[ 
            { 
               "description":"test",
               "amount":{ 
                  "currency":"AUD",
                  "value":12.1
               },
               "tax":{ 
                  "rate":10
               }
            }
         ]
      },
      { 
         "customerId":"2d9cd3e3-95f2-4add-8826-ba26901d784c",
         "paymentMethodToken":"d81f2651-0d09-4e2d-8541-4065f28e590b",
         "externalInvoiceId": "INV2-000101023",
         "memo": "this is a test invoice",
         "items":[ 
            { 
               "description":"test",
               "amount":{ 
                  "currency":"AUD",
                  "value":12.1
               },
               "tax":{ 
                  "rate":10
               }
            }
         ]
      }
   ]
}

Below is an example of the response produced by the above request:

HTTP/1.1 200
status: 200
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Tue, 05 Mar 2019 09:52:13 GMT
Expires: 0
Pragma: no-cache
Content-Length: 168
Connection: keep-alive

{
    "id": "19b4c222-94ef-4d68-8fcb-156155603ed1",
    "batchReference": "BATCH-REF-00000123",
    "createdOn": "2019-03-03T22:35:17.817",
    "status": "SUBMITTED"
}

Response object fields

FieldNullableTypeDescription
idNoUUIDThe globally unique identifier assigned to the invoice batch by v2
batchReferenceYesStringA globally unique reference identifier generated by the integrator
createdOnNoStringAn ISO 8601 compliant UTC timestamp representing the time at which the invoice batch record was created in v2
statusNoStringThe status of the invoice batch - described below

Each invoice batch has an associated status value representing the current workflow state of the entity within the context of v2. Possible status values and their meaning are described below:

  • SUBMITTED: This is the initial default status assigned to every invoice batch. This status value represents a state where the invoice batch has been received and enqueued for processing.
  • PROCESSING: This status value represents a state where the invoice batch is being processed by v2.
  • SUCCESS: This status value indicates that the invoice batch has been successfully processed and that all encapsulated on-demand invoice objects have either been used to create an on-demand invoice in v2 or have resulted in an unrecoverable error. It is important to note that integration partners are able to provide an optional batchReference property in each invoice batch object. This value should be globally unique to the merchant associated with the invoice batch creation request; submitting invoice batch requests including batchReference values that are already associated with a previously submitted invoice batch request will result in an error response. The inclusion of this value is intended to support correlation between webhook events emitted by v2 and extant invoice batch records within partner systems. This provides a mechanism for integration partners to maintain a representation of the state of each submitted batch within their own systems without the need to make additional requests to the v2 API. The structure of an invoice batch object, including basic on-demand invoice objects, is described below:
FieldRequiredTypeDescription
batchReferenceYesStringA globally unique reference identifier generated by the integrator
invoicesYesListA list of on-demand invoice objects

Invoice object

FieldRequiredTypeDescription
customerIdYesUUIDThe unique identifier of the customer to whom the invoice related
paymentMethodTokenYesUUIDThe tokenized payment method that should be used to attempt payment against the invoice
externalInvoiceIdNoStringA globally unique reference identifier generated by the integrator
memoNoStringA note describing the context of the invoice
itemsNoListA list of InvoiceItem objects

InvoiceItem object

FieldRequiredTypeDescription
descriptionYesStringA description of the invoice line item
amountYesAmount objecte.g. { "currency":"AUD", "value":100.00 }
taxYesTax objecte.g. { "rate":10 }

🚧

Important!

It is strongly recommended that integration partners implement webhook security in order to to verify the authenticity and origin of webhook requests.

Webhook events

There are three webhook events associated with the processing of invoice batches that integration partners are able to observe via registration with the webhook API:INVOICE_BATCH_CREATED: the emission of this event indicates that the associated invoice batch has been received and a record has been created in v2. Below is an example payload for this type of webhook event:

{ 
   "id":"df4921e1-9a85-4028-b0d0-088bb6263ed2",
   "batchReference":"BATCH-REF-00000123",
   "createdOn":"2019-03-03T00:31:38.296",
   "status":"SUBMITTED"
}

INVOICE_BATCH_PROCESSING: the emission of this event indicates that the associated invoice batch is currently being processed by v2. Below is an example payload for this type of webhook event:

{ 
   "id":"df4921e1-9a85-4028-b0d0-088bb6263ed2",
   "batchReference":"BATCH-REF-00000123",
   "createdOn":"2019-03-03T00:31:38.296",
   "status":"PROCESSING"
}

INVOICE_BATCH_SUCCESS: the emission of this event indicates that processing of the associated invoice batch has concluded successfully. Below is an example payload for this type of webhook event:

{ 
   "id":"df4921e1-9a85-4028-b0d0-088bb6263ed2",
   "batchReference":"BATCH-REF-00000123",
   "createdOn":"2019-03-03T00:31:38.296",
   "status":"SUCCESS"
}

INVOICE_BATCH_INVOICE_FAILED: the emission of this event indicates that an unrecoverable error occurred while attempting to create an invoice during the processing of an invoice batch. This event should be used to trap errors relating to specific invoices within a batch, allowing integrators to attempt invoice creation via the on-demand API if the error can be handled and rectified by the integrating system. Below is an example payload for this type of webhook event:

{ 
   "id":"269ad780-820a-44de-aa3d-900484eeb6c8",
   "invoiceBatchId":"e60be70e-05c0-4cc7-9b25-4482ffe27476",
   "invoiceBatchItemId":"3bd152dc-db80-4746-ae52-16e194c4a390",
   "externalInvoiceId":"INV3-Q8TP-5IME-AAKU-NG74",
   "failedReason":"External invoice id should be unique",
   "status":"FAILED",
   "failedOn":"2019-03-03T06:06:04.696"
}

Batch processing times

Integration partners can execute billing runs creating on-demand invoices via the invoice batch API at any time of day - when selecting a time to execute billing runs via the invoice batch API there are a number of factors to consider:

  • The time taken to process invoice batches is predicated on several factors - the size of the batch, the time of day, rate limits imposed by downstream bank payment systems in various markets, the type of payment method, the number of other batches currently in processing, etc. Ezypay cannot provide deterministic guarantees around the time it will take to process a batch of invoices, however a good general rule of thumb is that the system can generate around 1000 invoices per-hour, per-partner. Using this as a guide it is reasonable to assume that a batch of 5000 invoices should complete processing around five hours from the time it is submitted (or that five batches of 1000 invoices will complete processing in the same window).
  • If invoices included in a batch are due on the same day that they are being submitted you should ensure that the batch (or batches) are submitted for processing early enough in the day that processing will complete before midnight in the timezone in which the merchant operates. Invoices processed after midnight in the timezone in which the merchant operates will result in a validation error (and associated INVOICE_BATCH_INVOICE_FAILED webhook event) if the invoice date has lapsed even if the batch was submitted for processing before midnight. For example - if you are submitting batches of invoices for merchants in operating in Australia, and those invoices are due for payment on the day that the batch is submitted, then the batches should be submitted as early in the day as possible to ensure that processing (and the handling of any resulting errors) can complete before midnight (AEST).
  • Given that the invoice batch API uses the on-demand invoice creation API internally it is possible to leverage all the features provided by on-demand invoicing. Future dated invoices can be created via the invoice batch API, these invoices will be billed on the specified date in the timezone in which the applicable merchant resides. It is important to note that v2 does not support the cancellation or deletion of future dated invoices created via the on-demand invoice API at this time.

Integration steps

Below are the steps that integration partners should undertake in order to integrate with invoice batch API:

  1. Integration partners should subscribe to the following three webhook event types using the webhook API: INVOICE_BATCH_CREATED, INVOICE_BATCH_PROCESSING, INVOICE_BATCH_SUCCESS, INVOICE_BATCH_INVOICE_FAILED
  2. Once registered to observe the above events, integration partners will be able to submit invoice batch creation requests via the v2 API. It is important to note that each invoice batch is applicable to a single merchant - integration partners executing billing runs including invoices belonging to multiple merchants should ensure that invoices are grouped by merchant and submitted in separate requests to the invoice batch API using the correct access token and applicable merchant identifier. Once an invoice batch is submitted it will have a status of SUBMITTED - at this point an INVOICE_BATCH_CREATED webhook event will be emitted by v2. The invoice batch will remain in this state until it is selected for processing. When processing of the invoice batch has commenced the status of will transition to PROCESSING - at this point an INVOICE_BATCH_PROCESSING webhook event will be emitted by v2. During the processing of an invoice batch, v2 will emit all of the webhook events associated with regular and on-demand invoice processing (e.g. INVOICE_CREATED, INVOICE_PAST_DUE, INVOICE_PAID, INVOICE_UNPAID). The system will also emit instances of the INVOICE_BATCH_INVOICE_FAILED webhook event to signify that an unrecoverable error was encountered when attempting to create an invoice while processing the batch. INVOICE_BATCH_INVOICE_FAILED webhook events should be used by integrating systems to handle errors and determine whether or not to retry the creation of applicable invoices via the on-demand API. Finally, when the processing of an invoice batch is complete the record will be transitioned to a status of SUCCESS - at this point an INVOICE_BATCH_SUCCESS webhook event will be emitted by v2. When an INVOICE_BATCH_SUCCESS webhook event is received, the integration partner should make a call to the invoice batch detail API in order to retrieve information about the invoice batch and verify the status of each on-demand invoice encapsulated by the batch. Below is an example of a request to retrieve the details of an invoice batch:
GET /v2/billing/invoices/batches/f170ce3c-55cf-4eea-8945-5e33466de70e?cursor=0&limit=100
Content-Type: application/json
Cache-Control: no-cache
merchant: {your merchant identifier}
cache-control: no-cache
Authorization: Bearer {your JWT}
User-Agent: Integration Partner v1.0
Accept: */*
Host: api-global.ezypay.com
accept-encoding: gzip, deflate

Of particular note in the above example is the use of a cursor to paginate the items encapsulated by the invoice batch. In the case of large batches it may be necessary to execute several paginated requests to retrieve the state of all the items in the invoice batch; this can be achieved by nextUrl property of the paging object within the body of the response. Possible parameters for this resource are shown below:

FieldRequiredTypeDescription
idYesUUIDThe globally unique identifier of the invoice batch to retrieve details for
cursorNoIntegerAn integer value representing the page within the result set to retrieve records from
limitNoIntegerAn integer value representing the maximum number of records to include in response body
statusNoStringA filter specifying the status of the invoice batch items to query for - described below
  • PENDING: This status indicated that the invoice batch item has been selected for processing but that processing has not yet begun.
  • PROCESSING: This status indicates that the invoice batch item is currently being processed by v2.
  • SUCCESS: This status indicates that the invoice batch item was successfully processed and an on-demand invoice was created in v2.
  • FAILED: This status indicates that the invoice batch item failed with an unrecoverable error - when encountering this status, integration partners should attempt to retry the creation of the invoice by using the on-demand invoice API. Below is an example of a response to the above request:
HTTP/1.1 200
status: 200
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Tue, 05 Mar 2019 09:52:13 GMT
Expires: 0
Pragma: no-cache
Content-Length: 1696
Connection: keep-alive

{
    "id": "0cc78e64-1f70-4cd2-803b-4f307ae5fd72",
    "batchReference": "BATCH-REF-00000123",
    "createdOn": "2019-03-03T02:02:00.099",
    "status": "SUBMITTED",
    "invoiceBatchItemList": {
        "data": [
            {
                "id": "7ca55f41-7452-4b32-b6b6-040a97b43a94",
                "externalInvoiceId": "INV3-Q8TP-5IME-AAKU-NG74",
                "invoiceId": "999b826f-2cb5-490c-993b-3518b827e300",
                "status": "SUCCESS",
                "processingResult": "Invoice successfully created",
                "createdOn": "2019-03-03T02:02:00.099"
            },
            {
                "id": "7b89f65c-5235-4603-8dfe-72b08972528b",
                "externalInvoiceId": "INV3-Q8TP-5IME-AAKU-NG75",
                "invoiceId": "010361ad-9f66-42c9-8c90-2b1765680e94",
                "status": "SUCCESS",
                "processingResult": "Invoice successfully created",
                "createdOn": "2019-03-03T02:02:00.099"
            },
            {
                "id": "79add08a-5840-4ceb-b7b0-bed9ae2ab55e",
                "externalInvoiceId": "INV3-Q8TP-5IME-AAKU-NG76",
                "invoiceId": "8346a578-6f9b-4de4-b609-19dcdd294ab2",
                "status": "FAILED",
                "processingResult": "Validation error: external invoice identifier already exists for this merchant",
                "createdOn": "2019-03-03T02:02:00.099"
            }
        ],
        "paging": {
            "nextUrl": "https://api-global.ezypay.com/v2/billing/invoices/batches/e2e4b011-291c-4006-b555-6ddf4803e8d8?limit=10&cursor=10",
            "nextCursor": 30,
            "limit": 30,
            "totalCount": 500
        }
    }
}

The structure of an invoice batch object is described below:

FieldRequiredTypeDescription
idYesUUIDThe unique identifier of the requested invoice batch resource
batchReferenceNoStringA globally unique reference identifier generated by the integrator
createdOnYesStringAn ISO 8601 compliant UTC timestamp representing the time at which the invoice batch record was created in v2
statusYesStringA filter specifying the status of the invoice batch items to query for - described below
invoiceBatchItemListYesListA list of InvoiceBatchItem objects

InvoiceBatchItem structure*:

FieldRequiredTypeDescription
idYesUUIDThe unique identifier of the InvoiceBatchItem instance
externalInvoiceIdNoStringA globally unique reference identifier generated by the integrator
invoiceidNoUUIDThe unique identifier of the invoice associated with this invoice batch item
statusYesStringThe status of the invoice batch item - described below
processingResultNoStringA message indicating the outcome of invoice batch item processing - this could be affirmative or an error message
createdOnYesStringAn ISO 8601 compliant UTC timestamp representing the time at which the invoice batch record was created in v2

Page:

FieldRequiredTypeDescription
nextUrlYesURLThe URL to call to get the next result set
nextCursorNointegerThe value of the next page
limitNointegerThe maximum number of results to return in the result set
totalCountYesintegerA count representing the total number of records in the result set

It's also possible to list all batches belonging to a particular merchant using the request below:

GET /v2/billing/invoices/batches
Content-Type: application/json
Cache-Control: no-cache
merchant: {your merchant identifier}
cache-control: no-cache
Authorization: Bearer {your JWT}
User-Agent: Integration Partner v1.0
Accept: */*
Host: api-global.ezypay.com
accept-encoding: gzip, deflate

Request parameters for the above query are as follows:

FieldRequiredTypeDescription
cursorNointegerThe page in the result set to begin retrieving records
limitNointegerThe maximum number of records to return from the result set

Below is an example of a possible response to the above request:

HTTP/1.1 200
status: 200
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Tue, 05 Mar 2019 09:52:13 GMT
Expires: 0
Pragma: no-cache
Content-Length: 1696
Connection: keep-alive

{
    "data": [
        {
            "id": "a106a45c-aa81-4340-9796-18c007a6f40c",
            "batchReference": "657402c1-af23-4d8f-88d9-cc30d9e6318b",
            "createdOn": "2019-02-18T01:43:12.204",
            "status": "SUBMITTED"
        },
        {
            "id": "2b013405-ceca-46c6-aefe-f224f2266519",
            "batchReference": "657402c1-af23-4d8f-88d9-cc30d9e6318a",
            "createdOn": "2019-02-18T00:27:25.154",
            "status": "SUBMITTED"
        }
    ],
    "paging": {
        "nextUrl": "https://api-global.ezypay.com/v2/billing/invoices/batches?limit=2&cursor=2",
        "nextCursor": 2,
        "limit": 2,
        "totalCount": 80
    }
}

Below is a description of the structure and content of the above response body:

FieldRequiredTypeDescription
DataYesNoA list of abstract objects
PagingYesNoAn object encapsulating all the data required to process paging

Data:

FieldRequiredTypeDescription
idYesUUIDA unique identifier allocated to the invoice batch entity by v2
batchReferenceNoStringA globally unique reference identifier generated by the integrator
createdOnYesStringAn ISO 8601 compliant UTC timestamp representing the time at which the invoice batch record was created in v2
statusYesStringThe status of the invoice batch - described above

Paging:

FieldRequiredTypeDescription
nextUrlNoURLThe URL to call to get the next result set
nextCursorNointegerThe value of the next page
limitNointegerThe maximum number of results to return in the result set
totalCountYesintegerA count representing the total number of records in the result set