Pay By Bank Integration Process
Pay By Bank payments are made by creating payment requests via the API. Users are then redirected to the returned URL to complete their bank payment within the Stitch-hosted UI.
The integration below applies to all Pay By Bank methods (including Capitec Pay, Absa Pay, and PayShap RTP). A single payment request automatically presents all enabled methods to the payer. No method-specific code is required for a standard, low-risk integration.
For optional per-method overrides (verified flows, custom fields, disabling a specific method, or restricting to a single bank), see Method-Specific Configuration.
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:
When creating a payment initiation request, please note that:
- The
payerReferencefield is restricted to a maximum of 12 characters. - The
beneficiaryReferencefield is restricted to a maximum of 20 characters. - The
beneficiaryNamefield is restricted to a maximum of 20 characters. - The
externalReferencefield 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.
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.
Payer information
The payerInformation object should be specified with information of the payer on your system's records. The set of provided information is used to increase the efficacy of fraud risk checks done by Stitch. All possible inputs may be found found in the API reference.
At a minimum, the payerId should always be specified within a request. This may be any internal identifier that always uniquely identifies users across Stitch requests.
Metadata enrichment
The metadata field may be populated with any additional metadata relating to your payment request, such as further order information, shipping information, or sub-entity for which the payment is being processed.
This must be specified as an object of type Json, that includes key-value pairs. Note that nested objects must be JSON-stringified.
Recommended Metadata for Fraud Checks
To assist with fraud and risk checks performed on payments for physical goods, include shipping information within your metadata, as shown in the request examples. Values to be specified include:
billingAddressof the user,shippingAddressof the order,deliveryMethodnoting whether this is adeliveryorcollectionorder, andgiftCardAmountif a gift card or discount code was used in conjunction with the order's payment.
The merchant field that was previously used for this purpose, has been deprecated. Please use the metadata field instead.
Specifying a Merchant
The merchantId field is an optional Stitch-supplied merchant identifier that routes the payment to a pre-registered merchant on your client. This is typically used in multi-merchant configurations (e.g. marketplaces or aggregator integrations), where funds for the payment should be associated with a specific sub-merchant or registered entity rather than the default merchant configured on your client.
When supplied, the value must reference a merchant that has been pre-provisioned by Stitch against your client. An example value is shown below:
bWVyY2hhbnQvNzhjMzc2NjUtZTgyNi00MGEyLWJiZDUtOTQ1YWNlYjYzMTc1
The merchantId value is a Stitch resource identifier (not your acquirer-assigned MID). Each pre-registered merchant may be configured for a different subset of payment methods. Requirements per merchant will be aligned with you during onboarding.
For Pay by Bank, either beneficiary or merchantId must be specified. When merchantId is supplied, Stitch will settle the payment to the beneficiary on the pre-registered merchant configuration, and the beneficiary field may be omitted.
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:
- https://localhost:8080
- https://localhost:8080/return
- https://localhost:3000
- https://localhost:3000/return
- https://localhost:9000
- https://localhost:9000/return
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
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:
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 Field | Description | Type |
|---|---|---|
| id | The unique id of this payment request | ID |
| status | Status 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 payment | String |
| externalReference | The 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 Stitch | String |
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.
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:
- using the webhooks as the source of truth as they are secure and will always be sent from Stitch.
- 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:
| Status | Description |
|---|---|
| PaymentInitiationRequestCompleted | This is a final payment state. |
| PaymentInitiationRequestPending | The user hasn't yet completed the payment initiation request, or they exited the Stitch dialog box before completing the bank selection process. |
| PaymentInitiationRequestCancelled | The payment initiation request was manually cancelled by the client. More information on this can be found here. |
| PaymentInitiationRequestExpired | The 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:
PaymentInitiationRequestCompletedPaymentInitiationRequestCancelledPaymentInitiationRequestExpired
Example Payload
{
"data": {
"client": {
"paymentInitiationRequests": {
"node": {
"__typename": "PaymentInitiationRequest",
"amount": {
"currency": "ZAR",
"quantity": "1"
},
"bankBeneficiaries": [
{
"__typename": "BankBeneficiary",
"accountNumber": "1234567890",
"bankAccountNumber": "1234567890",
"bankId": "nedbank",
"name": "FizzBuzz Co."
}
],
"beneficiaries": [
{
"__typename": "BankBeneficiary",
"accountNumber": "1234567890",
"bankAccountNumber": "1234567890",
"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": "1234567890",
"bankAccountNumber": "1234567890",
"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.
| Status | Description |
|---|---|
| IncorrectLoginCredentials | The user entered incorrect login credentials when attempting to login. |
| LoginMultifactorFailed | Login multifactor authentication was failed or was rejected. |
| LoginMultifactorTimeout | Login multifactor authentication timed out while awaiting approval. |
| PaymentMultifactorFailed | Payment multifactor authentication failed or was rejected. |
| PaymentMultifactorTimeout | Payment multifactor authentication timed out while awaiting approval. |
| BeneficiaryMultifactorFailed | Beneficiary multifactor authentication failed or was rejected. |
| BeneficiaryMultifactorTimeout | Beneficiary multifactor authentication timed out while awaiting approval. |
| InsufficientFundsForPayment | The bank rejected the payment due to the account having insufficient funds. |
| DuplicatePayment | The bank does not allow duplicate payments. |
| InvalidSourceAccount | The source account was not valid. |
| InvalidDestinationAccount | The destination account was not valid. |
| PaymentLimitsExceeded | The bank declined the payment because the payment exceeded the current limits. |
| PaymentMinimumNotMet | The bank declined the payment due to payment minimum not met. |
| BankTimeout | The bank timed out while processing a request. |
| BankUnavailable | The bank was partially or fully unavailable while processing a request. |
| BankError | An error occurred at the bank while processing a request. |
| AccountBlockedByBank | Request was rejected by the bank due to the account being blocked. |
| PaymentDeclinedByBank | The 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:
| Status | How to Test |
|---|---|
| InvalidDestinationAccount | Use a beneficiary account that starts with 999 |
| PaymentLimitsExceeded | Use a payment amount above one million ZAR |
| PaymentMinimumNotMet | Use a payment amount less than ZAR 1.00 |
| DuplicatePayment | Set beneficiaryRef as KombuchaFizz and amount ZAR 10.00 |
| InsufficientFundsForPayment | Use a payment amount greater than the respective account balance |
| PaymentMultifactorFailed | Use an account number that starts with 888 |
| PaymentMultifactorTimeout | Use 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.
Requesting Pay By Bank Refunds
To reverse any funds back to the payer for a successful transaction, see our separately documented Pay By Bank Refunds API calls.
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
paymentRequestIdof the pending payment request to be completed, and - The
paymentMethodspecified with the valueeft.
In addition to the above required fields, you may optionally specify the following to affect the completion result:
- The
eft.fromBankas the payer bank to simulate completion for, and - The
eft.fromAccountNumberas 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.
Method-Specific Configuration
The sections below document optional overrides for individual payment methods. If you are using the standard integration above, all enabled methods work automatically and you do not need any of the configuration below.
Capitec Pay
Capitec Pay is included by default when enabled on your client. Additional configuration is only needed if:
- Capitec deems your business high-risk and you require a Verified Flow;
- Your business is a TPPP (third-party payment provider) and facilitates payments to sub-merchants; and/or
- You wish to provide Capitec Pay as a standalone payment option.
Capitec Pay Verified Flow
Should Capitec require that you implement the "Verified flow", you will need to include the payer's verified identifier when creating a payment request.
Only the payer's South African Identity Number can be used as a verified identifier. If a value is provided, it will only be validated when paying with Capitec Pay.
The identityNumber must be a valid South African ID number. Passport numbers are not supported.
If an invalid ID number is provided, the Capitec Pay payment initiation request will fail with a message indicating that a valid South African ID number is required.
Within the payment request mutation, initiating the verified flow on Capitec Pay is as follows:
Unverified users
If the user selects Capitec from the list of banks while attempting to make their payment, and the identifier passed in the above
mutations is either invalid, missing, or is not associated with a Capitec bank account they will be presented with a message indicating
that they need to verify their details before being redirected back to your site with reason=verification_required.
Capitec Pay Merchant Specification for TPPPs
For clients that will operate as a Third-Party Payment Provider (TPPP), it is required to specify a merchant reference when initiating Capitec Pay payments. This reference should uniquely identify the sub-merchant that the payment is being initiated for, and should be the same reference specified as part of the merchant's Capitec onboarding.
This reference should be provided in the merchantDetails.capitecPayMerchantReference field. An example of a payment initiation request including this is shown below:
If your client does not have the permission to provide a capitecPayMerchantReference, an error saying Client does not have the required permissions to set a custom Capitec Pay merchant reference. will be presented, please reach out to support@stitch.money to resolve this.
Handling non-nominated merchants
When your client is enabled for Capitec Pay, this method will be enabled in all payment requests by default. If your sub-merchant has not been onboarded with Capitec,
payment requests for this merchant will need to have the option explicitly disabled, by setting the paymentMethods.eft.capitecPay.enabled field to false.
An example of a request that would have Capitec Pay disabled is shown below:
Restricting Payer Bank to Capitec
It is possible to display Capitec Pay as a separate payment method in your app by including the restrictPayerBank field in mutations on the Stitch API.
If restrictPayerBank is set to "capitec" the payer will only be able to link/make payments with Capitec Pay. An example request is shown below:
Error Handling (with verified flow)
If you are using the verified flow and you restrict the payer bank to "capitec", the API will validate the identifier you provide.
This is because the payer would be unable to proceed at all if the identifier is missing or invalid, as they would not be able to select another bank.
Should this validation fail, an error will be returned. Possible error scenarios and associated response codes/messages are:
| Error scenario | Error Code | Error Message |
|---|---|---|
| Invalid identity number provided | BAD_USER_INPUT | Expected identity number to be a valid South African identity number. |
| No identifier provided | VERIFICATION_REQUIRED | A verified South African identity number or Capitec account number is required in order to authorize an account using Capitec Pay on the verified ID flow. |
Testing Capitec Pay Scenarios
Our sandbox environment allows simulation of specific Capitec Pay scenarios that users may experience in production, including how a user could handle MFA (multi-factor authentication) in their Capitec app. To simulate these cases, the beneficiaryReference of your payment request can be tweaked as follows:
| Scenario | Beneficiary Reference |
|---|---|
| MFA successful and payment completed | success |
| MFA request pending completion | success-pending |
| MFA request timed out | success-timeout |
| MFA request declined by user | success-declined |
| Payment initiation failure at Capitec | success-failed |
| Payment marked as fraudulent by user | success-fraud |
Absa Pay
Absa Pay is included by default when enabled on your client. The following optional fields can be passed in to customise the Absa Pay flow:
| Request Field | Description | Type |
|---|---|---|
| enabled | By default, the Absa Pay payment method is enabled for all payment requests. Use this optional field to disable Absa Pay at the payment request level, replacing it with the Absa iEFT implementation. | Boolean |
| payerDisplayNarrative | This is the description of the goods or services that the user is purchasing. This is defaulted to {payee_name} payment. | String |
| merchantDetails.name | This is used to customise the payee name field which is shown on the approval prompt in the Absa application. This is defaulted to the configured client name. | String |
Within the payment request mutation, this looks as follows:
Absa Pay Verified Flow
Should Absa require that you implement the "Verified flow", you will need to include the payer's verified identifier when creating a payment request.
A payer's South African ID number or foreign passport number must be used as a verified identifier. If a South African ID number is provided, it will only be validated when making payments via Absa Pay.
The identityNumber must be a valid South African Identity Number.
If an invalid South African Identity Number is provided, the Absa Pay payment initiation request will fail with a message indicating that a valid South African ID number is required.
Within the payment request mutation, initiating the verified flow on Absa Pay is as follows:
Unverified users
If the user selects Absa Pay from the list of banks while attempting to make their payment, and the identifier passed in the above
mutations is either invalid, missing, or is not associated with an Absa bank account they will be presented with a message indicating
that they need to verify their details before being redirected back to your site with reason=verification_required.
Testing Absa Pay Scenarios
Our sandbox environment allows simulation of specific Absa Pay scenarios that users may experience in production, including how a user could handle MFA (multi-factor authentication) in their Absa app. To simulate these cases, the beneficiaryReference of your payment request can be tweaked as follows:
| Scenario | Beneficiary Reference |
|---|---|
| MFA successful and payment completed for user with single account | success |
| MFA successful and payment completed for user with multiple accounts | success-multiple |
| MFA request timed out | success-timeout |
| MFA request declined by user | success-declined |
| Insufficient funds | success-insufficient |
| Daily limit exceeded | success-dailyLimit |
| MFA successful, but payment fails | success-paymentFail |
| Payment initiation failure at Absa | failed |
| Payment initiated from an account without an Absa application | notADigitalCustomer |
| Payment initiated from non-existent Absa account | noAccountFound |
| Payment initiated for a beneficiary that has already been paid | paidBeneficiary |
| Payment initiated from a business account that cannot initiate Absa Pay payments | invalidBusinessAcc |
| Duplicate payment initiated | duplicatePayment |
PayShap Request (RTP)
RTP requires no code changes. When enabled for a given bank on your client, it automatically replaces the standard Pay By Bank tile for that bank in the Stitch-hosted UI. The standard payment request creation mutation is used unchanged.
Each bank has different limits and fees that apply to RTP payments. Stitch will only use RTP if the payment request amount is less than the bank-imposed limit. The standard Pay By Bank flow is used as a fallback for payments above this amount. See the RTP bank limits table for details.
RTP payments use the same webhooks (subscribe to payment events) and the same refund process as all other Pay By Bank methods.
Testing RTP Scenarios
Our sandbox environment allows simulation of specific RTP scenarios that users may experience in production. To simulate these cases, enter one of the following account or mobile numbers when simulating the user journey to surface the corresponding scenario.
| Scenario | Account number | Mobile number |
|---|---|---|
| Successful MFA and payment | 999999999 | 0841233210 |
| MFA declined by user | 999999990 | 0841234567 |
| MFA timed out without approval | 999999991 | 0841234568 |
| User supplied invalid credentials | 999999992 | 0841234569 |
| User supplied credentials that are no longer active | 999999993 | 0841234570 |
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.