Skip to main content

Pay By Bank Integration Process

Pay by Bank payments can be made easily by creating payment requests via the API. Users can then be guided to the returned redirect URLs, for them to complete their bank payments within the Stitch-hosted UI.

Generate Payment Request URL

Pay By Bank creation is protected by a client token. You'll need to follow the steps described in the client token guide to obtain a client token with the client_paymentrequest scope. To create a payment initiation request, you'll need to ensure that the feature is enabled on your client.

To create the request, a GraphQL mutation is used to specify the requested amount, the references that will appear on the user and beneficiary's accounts, and the beneficiary details themselves.

The GraphQL API URL https://api.stitch.money/graphql can be used for all payment requests (whether on test or live clients).

An example of a GraphQL request to create a payment initiation request is shown below:

Character Limits

When creating a payment initiation request, please note that:

  • The payerReference field is restricted to a maximum of 12 characters.
  • The beneficiaryReference field is restricted to a maximum of 20 characters.
  • The beneficiaryName field is restricted to a maximum of 20 characters.
  • The externalReference field is restricted to a maximum of 4096 characters.

The two pieces of information you need from the response at this stage are the payment request id, and the url. The payment request id is used to correlate responses and to look up the status of the request in the API, and so should be retained for later usage. The url is used to enable the user to authorize the payment request, and you'll need to redirect the user to this URL. We'll cover this in the next section.

Why can you specify more than one bank account?

If you're eagle-eyed, you may have noticed that you can specify either a single bankAccount or a list of bankAccounts when creating a payment request.

The bank account list was added because many companies open accounts at a number of different banks, and instruct users to pay into the same institution that they bank with. Companies often opt for this approach as intra-bank transfers typically clear near instantaneously.

Payment requests automate this account selection process by selecting the optimal account from the list for a given user.

The first bank account in the list acts as the preferred account and will be selected if a more optimal route is not found.

Expiring payment requests

It is highly recommended that an expireAt Date (ISO 8601) is supplied in the creation of any payment initiation request. At the specified date and time, the payment request status will automatically move to PaymentInitiationRequestExpired, if the payment is not yet successfully completed.

Surface URL and Handle Callback

The URL returned by the API requires that a whitelisted redirect_uri is appended to it as a query string argument. If you direct a user to this URL, they will be guided through the process of completing the payment. For test clients, we do have the following URLs whitelisted by default:

For example, if your whitelist URL configuration included the URL https://example.com/payment for payment requests, you'd append the following query string to the url returned from the API: ?redirect_uri=https%3A%2F%2Fexample.com%2Fpayment. The full URL you expose to the user should look like this

https://secure.stitch.money/connect/payment-request/2b068bd5-6a5a-42e1-8a45-673cb3ede612?
redirect_uri=https%3A%2F%2Fexample.com%2Fpayment
Whitelisting Redirect URLs

To add or remove a URL from the whitelist, please reach out to the Stitch team.

Please note that production clients will not be allowed to use unsecure redirect_uri params such as http. For example:

http://example.com/payment

https://example.com/payment

Once the user completes or cancels the payment request, they'll be redirected back to the redirect_uri. The below query string parameters will be passed back to the redirect_uri.

Request FieldDescriptionType
idThe unique id of this payment requestID
statusStatus will have the value complete if successful, closed if the user clicked close in the UI, or failed if something went wrong when attempting the paymentString
externalReferenceThe value that was provided to the externalReference field when the payment initiation request was created. It can be used to correlate transaction IDs within your system with the payment request initiated by StitchString

The id can be used to retrieve the final payment request status and other details from the Stitch API, as well as relate to incoming webhooks.

danger

The status field should not be used for any database operations since an external party can tweak it. To secure yourself from attackers who can send a fake payload to your redirect or webhook endpoint, we recommend:

  1. using the webhooks as the source of truth as they are secure and will always be sent from Stitch.
  2. making use of signed webhooks since you'll be able to verify the signature of each incoming webhook's request payload.

Using the webhooks also ensures you'll still be able to know the final status of a payment request if for example the request times out due to a network issue.

Using Multiple Internal Redirect URIs

For a situation where different callbacks lead to different internal URLs, you SHOULD have a single whitelisted redirect URL which can then have the logic handling the Stitch callback and redirect to the internal URLs. An overview of how to do this in NodeJS is as shown below:

const status = params.status;

switch (status) {
case "complete":
redirect("/eft-success");
case "closed":
case "failed":
redirect("/eft-retry");
default:
break;
}

Cancelling Pending Payment Initiation Requests

If the user clicks the X on the dialog box, the payment initiation request will remain in the PaymentInitiationRequestPending status. To cancel the payment request, you should call the clientPaymentInitiationRequestCancel mutation, which also triggers the cancel webhook event.

Payment Initiation Request Statuses

The table below describes the different statuses a Pay By Bank request can have, with the initial status always being PaymentInitiationRequestPending:

StatusDescription
PaymentInitiationRequestCompletedThis is a final payment state.
PaymentInitiationRequestPendingThe user hasn't yet completed the payment initiation request, or they exited the Stitch dialog box before completing the bank selection process.
PaymentInitiationRequestCancelledThe payment initiation request was manually cancelled by the client. More information on this can be found here.
PaymentInitiationRequestExpiredThe payment initiation request has expired while awaiting user interaction. More information on this can be found here.

Subscribe to Webhooks

To receive a webhook upon payment completion, payment cancellation or expiry you will need to create a subscription for your client. Please note that this will always send a signed webhook for your Pay By Bank requests. You can read more about how to verify the signature within the webhook event in our more detailed guide here

If the subscription is successfully created, the body returned by the request will look like the sample in the Example Response tab in widget above.

For more information on receiving webhook events, listing active webhook subscriptions, unsubscribing from webhooks and validating signed webhook subscriptions, please visit the Webhooks page.

Webhook Statuses

The payment webhook will be dispatched for each of the following status updates:

  • PaymentInitiationRequestCompleted
  • PaymentInitiationRequestCancelled
  • PaymentInitiationRequestExpired
Example Payload
{
"data": {
"client": {
"paymentInitiationRequests": {
"node": {
"__typename": "PaymentInitiationRequest",
"amount": {
"currency": "ZAR",
"quantity": "1"
},
"bankBeneficiaries": [
{
"__typename": "BankBeneficiary",
"accountNumber": "1223273660",
"bankAccountNumber": "1223273660",
"bankId": "nedbank",
"name": "FizzBuzz Co."
}
],
"beneficiaries": [
{
"__typename": "BankBeneficiary",
"accountNumber": "1223273660",
"bankAccountNumber": "1223273660",
"bankId": "nedbank",
"name": "FizzBuzz Co."
}
],
"beneficiaryReference": "KombuchaFizz",
"cancellationReason": null,
"created": "2022-10-11T09:52:13.312Z",
"createdAt": "2022-10-11T09:52:13.312Z",
"currency": "ZAR",
"events": [],
"externalReference": "79261d16-c53b-48eb-9019-dc9cfb6c5126",
"failureReason": null,
"id": "cGF5cmVxLzk2YjUyODU1LTBkMzQtNDI0MS04YmM2LWE4ODBlMDQ1ZGQzOQ==",
"payerConstraints": null,
"payerReference": "Joe-Fizz-01",
"paymentConfirmation": {
"__typename": "PaymentPending",
"date": "2022-10-11T09:52:53.128Z"
},
"paymentMethods": [
{
"cash": {
"atm": {
"enabled": true
},
"retailer": {
"barcodeNumber": "test-11132098754321",
"barcodeUrl": "https://test-barcode-url.com",
"enabled": true
}
}
},
{
"eft": {
"enabled": true
}
}
],
"paymentRequestPayerConstraintRelationship": null,
"quantity": "1",
"refunds": [],
"state": {
"__typename": "PaymentInitiationRequestCompleted",
"amount": {
"currency": "ZAR",
"quantity": 1
},
"beneficiary": {
"__typename": "BankBeneficiary",
"accountNumber": "1223273660",
"bankAccountNumber": "1223273660",
"bankId": "nedbank",
"name": "FizzBuzz Co."
},
"date": "2022-10-11T09:52:53.114Z",
"id": "96b52855-0d34-4241-8bc6-a880e045dd39",
"payer": {
"__typename": "PaymentInitiationBankAccountPayer",
"accountName": "Current account",
"accountNumber": "4104754941",
"accountType": "current",
"bankId": "absa"
},
"proofOfPayment": null
},
"updated": "2022-10-11T09:52:53.128Z",
"updatedAt": "2022-10-11T09:52:53.128Z",
"url": "https://secure-local.stitchmoney.com/connect/payment-request/96b52855-0d34-4241-8bc6-a880e045dd39",
"userReference": "Joe-Fizz-01"
}
}
}
}
}

Retrieving Payment Request Status

When receiving a callback from the payment request, you will likely want to check the status of the payment. Using the query below you can retrieve the status of a given payment by ID, in addition to information about the payer and beneficiary involved.

If a user closed the payment request interface, and expireAt was supplied in the payment initiation creation request, the payment will remain in the PaymentInitiationRequestPending state until the expireAt date has passed. The request will only be in the PaymentInitiationRequestCancelled state when the clientPaymentInitiationRequestCancel mutation is called.

When a user has successfully initiated the payment, the state will be PaymentInitiationRequestCompleted and contain details of the payment, including the account selected and the specific beneficiary that the payment was made to.

To retrieve the status of a payment request, as described above, you'll need a client token with the client_paymentrequest scope.

Payment Event Types

The events field can provide insight in to the events that occurred during the payment process. To retrieve the events of a payment request, you'll need a client with the paymentevents alpha flag. Payment event __typename and descriptions are outlined in the table below.

StatusDescription
IncorrectLoginCredentialsThe user entered incorrect login credentials when attempting to login.
LoginMultifactorFailedLogin multifactor authentication was failed or was rejected.
LoginMultifactorTimeoutLogin multifactor authentication timed out while awaiting approval.
PaymentMultifactorFailedPayment multifactor authentication failed or was rejected.
PaymentMultifactorTimeoutPayment multifactor authentication timed out while awaiting approval.
BeneficiaryMultifactorFailedBeneficiary multifactor authentication failed or was rejected.
BeneficiaryMultifactorTimeoutBeneficiary multifactor authentication timed out while awaiting approval.
InsufficientFundsForPaymentThe bank rejected the payment due to the account having insufficient funds.
DuplicatePaymentThe bank does not allow duplicate payments.
InvalidSourceAccountThe source account was not valid.
InvalidDestinationAccountThe destination account was not valid.
PaymentLimitsExceededThe bank declined the payment because the payment exceeded the current limits.
PaymentMinimumNotMetThe bank declined the payment due to payment minimum not met.
BankTimeoutThe bank timed out while processing a request.
BankUnavailableThe bank was partially or fully unavailable while processing a request.
BankErrorAn error occurred at the bank while processing a request.
AccountBlockedByBankRequest was rejected by the bank due to the account being blocked.
PaymentDeclinedByBankThe bank declined the payment.
Testing Pay By Bank Events

Not all events are easily reproducible. Some of the events which can be simulated for test clients are as follows:

StatusHow to Test
InvalidDestinationAccountUse a beneficiary account that starts with 999
PaymentLimitsExceededUse a payment amount above one million ZAR
PaymentMinimumNotMetUse a payment amount less than ZAR 1.00
DuplicatePaymentSet beneficiaryRef as KombuchaFizz and amount ZAR 10.00
InsufficientFundsForPaymentUse a payment amount greater than the respective account balance
PaymentMultifactorFailedUse an account number that starts with 888
PaymentMultifactorTimeoutUse an account number that ends with 888

Optional Queries

Viewing all Initiated Payments

To view the collection of initiated payments, you may query the Client.paymentInitiationRequests field on the API. This will return a paged collection of initiations, dated from most recent to oldest.

Note that this request needs a client token with the client_paymentrequest scope.

Retrieving Payment by External Reference

If the clientPaymentInitiationRequestCreate mutation was supplied with an externalReference representing the unique identifier for the payment on your system, you can then use this reference as a filter for the payment request and get back the details.

When a user has successfully completed the payment, the status will be PaymentInitiationRequestCompleted and contain details such as the payer and the date it was completed.

Note that this request needs a client token with the client_paymentrequest scope.

Simulating Completed Payment Requests

When using your test client, you may simulate completion of a payment request, directly over the API. This replicates payment updates that would be made when a real Pay by Bank payment is completed on a live client.

This is done by running the mutation shown below, containing the following input values:

  • The paymentRequestId of the pending payment request to be completed, and
  • The paymentMethod specified with the value eft.

In addition to the above required fields, you may optionally specify the following to affect the completion result:

  • The eft.fromBank as the payer bank to simulate completion for, and
  • The eft.fromAccountNumber as the bank account to simulate completion for.

This results in the payment request moving to the final status PaymentInitiationRequestCompleted. A corresponding webhook notification will also be sent to your configured receiving endpoint.

Postman Collection

Pay by Bank requests can be created and tested using the Postman collection available here. These can be used by specifying your client credentials and supplying the custom request variables where required.