Skip to main content

Integrating Once-off Card with Stitch-Hosted UI

Once-off Card 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 card payments within the Stitch-hosted UI.

This page is primarily server-side (create the payment request). Your client only needs to redirect the user to the returned URL.

Once-off Card with Hosted UI

Server-side Hosted UI once-off Card: create the payment request, redirect the user to Stitch, then handle return and webhooks.

Generate Payment Request URL

Much like Pay By Bank, Card 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, references and merchant information.

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 with the Card payment method is shown below:

Card Payment Method Details

Please note that:

  • The paymentMethods.card type requires client configuration. Contact the technical team to assist in enabling your client for this feature.
  • Not setting the paymentMethods field results in the Pay by Bank method being enabled on your payment request. To explicitly omit Pay by Bank as payment method, specify the paymentMethods.eft.enabled field with a value of false.

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.

note

This must be specified as an object of type Json, that includes key-value pairs. Note that nested objects must be JSON-stringified.

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:

  • billingAddress of the user,
  • shippingAddress of the order,
  • deliveryMethod noting whether this is a delivery or collection order, and
  • giftCardAmount if a gift card or discount code was used in conjunction with the order's payment.
Deprecated field

The merchant field that was previously used for this purpose, has been deprecated. Please use the metadata field instead.

3DS-enforcement

Typically, 3DS-enforcement rules are automatically determined by Stitch for all requests processed on your client, as described here.

However, if there is a requirement to specify 3DS-enforcement on a per-request basis, the paymentMethods.card.requireSecure3d parameter can be used to enable or disable 3DS enforcement. Contact the technical team to assist in enabling your client for this feature.

Supplying Your Own 3DS Results

If you perform 3DS authentication externally (outside of Stitch), you can supply your authentication results directly via the paymentMethods.card.secure3d input object. When provided, Stitch will not initiate any 3DS, but rather process the transaction with your given authentication data.

The secure3d object requires three fields:

FieldTypeDescription
eciString (required)The Electronic Commerce Indicator from your 3DS authentication. Scheme-specific values apply (Visa: 05/06/07, Mastercard: 00/01/02/04).
cryptogramString (required)The CAVV (Cardholder Authentication Verification Value), a Base64-encoded cryptographic proof that 3DS authentication was performed.
dsTransactionIdString (required)The Directory Server Transaction ID from the 3DS 2.x authentication flow.

Example values (to be supplied within a transaction initiation):

{
"paymentMethods": {
"card": {
"secure3d": {
"eci": "05",
"cryptogram": "AJkBBkhgQRhgRTGHCQADCAAAAAA=",
"dsTransactionId": "f25084f0-5b16-4c0a-ae5d-b24e5e631c1a"
}
}
}
}
Validation Rules

The secure3d object cannot be combined with requireSecure3d: true. Supplying both will result in a validation error, since requireSecure3d: true instructs Stitch to perform 3DS internally, which conflicts with externally supplied 3DS results.

The following combinations are valid:

requireSecure3dsecure3d presentBehavior
trueNoStitch performs 3DS internally (existing flow)
trueYesValidation error
falseNo3DS is skipped entirely (existing flow)
falseYesStitch bypasses internal 3DS and forwards your data to the processor
unsetNoStitch performs 3DS internally (existing flow)
unsetYesStitch bypasses internal 3DS and forwards your data to the processor

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
Provisioning Merchants

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.

The optional externalMerchantId field is an alternative way to reference the same pre-registered merchant, using your own identifier instead of the Stitch resource identifier. This is useful where your systems already key merchants by an internal identifier, and you would prefer not to store the Stitch-supplied value alongside it.

The value is agreed with Stitch during merchant onboarding and recorded against the merchant's configuration, so you do not generate one per request. It is unique across the merchants registered on your client, between 1 and 64 characters long, and made up of letters, numbers, full stops, underscores and hyphens. An example value is shown below:

store-4471

At most one of merchantId or externalMerchantId may be supplied. Supplying both, or supplying an externalMerchantId that is not registered against your client, fails with a BAD_USER_INPUT error. The payment is never silently routed to the default merchant configured on your client.

Where the request also accepts a beneficiary, that field and the merchant are mutually exclusive. Settlement is directed to the beneficiary registered against the merchant, so supplying both fails with a BAD_USER_INPUT error.

Surface URL and Handle Callback

The URL returned by the API needs a redirect_uri on an allowlisted domain, appended as a query string parameter. Send your customer to that URL and they'll be guided through the payment request. Test clients have these domains allowlisted by default:

The allowlist covers whole domains, so any path on an allowlisted domain works. If https://example.com is on your allowlist, then https://example.com/payment is a valid redirect_uri, and the full URL you expose to your customer looks like this:

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

To add or remove a domain, reach out to the Stitch team.

Production clients can't use an insecure (http) redirect_uri:

http://example.com/payment

https://example.com/payment

Once your customer completes or cancels the payment request, they're redirected back to the redirect_uri with these query string parameters:

FieldDescriptionType
idThe unique Stitch ID of this payment request.ID
statuscomplete if the payment request was completed successfully, closed if the customer closed the UI, or failed if something went wrong.String
payment_methodThe method used to complete the payment request: card or eft.String
externalReferenceThe value you supplied in the externalReference field when creating the payment request. Use it to match the payment request against your own records.String

Use the id to retrieve the final payment request status from the Stitch API, and to match against incoming webhooks.

danger

Don't use status for database operations — anyone can tamper with a query string parameter. To protect yourself from a fake payload sent to your redirect or webhook endpoint:

  1. Treat webhooks as your source of truth. They're always sent by Stitch.
  2. Use signed webhooks, so you can verify the signature on every incoming payload.

Webhooks also mean you still learn the final status of a payment request if the redirect never arrives — for example, if the request times out.

Alternative Redirect URI on Failure

Optionally, you can append a failure_redirect_uri to the URL returned from the API — e.g. ?redirect_uri=https%3A%2F%2Fexample.com%2Fpayment&failure_redirect_uri=https%3A%2F%2Fexample.com%2Fpayment%2Ffailure.

If this parameter is supplied, users will only be redirected to the redirect_uri when the status is complete, and to the failure_redirect_uri when the status is closed or failed. In other words, users that successfully complete a payment are directed to the redirect_uri, and those that exit without completing a payment are directed to the failure_redirect_uri.

The failure_redirect_uri receives the same query parameters and is subject to the same validations as the redirect_uri, including requiring the secure https:// protocol for production clients, and being validated against the configured redirect URI whitelist for your client.

Using Multiple Internal Redirect URIs

If different outcomes should land your customer on different pages, keep a single allowlisted redirect domain and branch to your internal URLs from there. An example is shown below, in NodeJS:

const status = params.status;

switch (status) {
case "complete":
redirect("/success");
break;
case "closed":
case "failed":
redirect("/retry");
break;
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.

Automatic cancellation

Optionally, when the user clicks the X on the dialog box, the payment initiation request can be automatically cancelled instead. This will subsequently invoke the status update, and corresponding webhook notification.

If you would like this feature enabled on your client, reach out to the Stitch team to have it configured.

Payment Initiation Request Statuses

A Card Payment Initiation Request always starts in the PaymentInitiationRequestPending state and then transitions into exactly one terminal state. The diagram below shows the possible transitions, and the table that follows describes each status in detail.

StatusDescription
PaymentInitiationRequestCompletedThis is a final payment state, indicating the user's payment was successful.
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 (or cancelled on user exit if the feature is enabled for your 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

Since Card is similar to Pay By Bank, we subscribe to webhooks and receive payment updates in the same way.

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": "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,
"feeAmount": null,
"id": "cGF5cmVxLzk2YjUyODU1LTBkMzQtNDI0MS04YmM2LWE4ODBlMDQ1ZGQzOQ==",
"payerConstraints": null,
"payerReference": "Joe-Fizz-01",
"paymentConfirmation": {
"__typename": "PaymentPending",
"date": "2022-10-11T09:52:53.128Z"
},
"netAmount": null,
"paymentMethods": [
{
"cash": {
"atm": {
"enabled": true
},
"retailer": {
"barcodeNumber": "test-11132098754321",
"barcodeUrl": "https://test-barcode-url.com",
"enabled": true
}
}
},
{
"eft": {
"enabled": true
}
}
],
"paymentRequestPayerConstraintRelationship": null,
"primarySettlement": null,
"quantity": "1",
"refunds": [],
"splitSettlements": null,
"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"
}
}
}
}
}

Split settlements

For clients with split settlements enabled, the payment initiation request node for a split card payment includes the settlement allocation and fee breakdown. primarySettlement.amount is the transaction amount less the sum of the splits, and each splitSettlements entry gives a destination merchant and its portion. The feeAmount and netAmount figures are exposed at the top level of the node, on primarySettlement, and on each splitSettlements entry.

{
"feeAmount": { "currency": "ZAR", "quantity": "4.25" },
"netAmount": { "currency": "ZAR", "quantity": "395.75" },
"primarySettlement": {
"amount": { "currency": "ZAR", "quantity": "280.00" },
"feeAmount": { "currency": "ZAR", "quantity": "2.93" },
"netAmount": { "currency": "ZAR", "quantity": "277.07" }
},
"splitSettlements": [
{
"merchantId": "bWVyY2hhbnQvNzhjMzc2NjUtZTgyNi00MGEyLWJiZDUtOTQ1YWNlYjYzMTc1",
"amount": { "currency": "ZAR", "quantity": "120.00" },
"feeAmount": { "currency": "ZAR", "quantity": "1.32" },
"netAmount": { "currency": "ZAR", "quantity": "118.68" }
}
]
}

primarySettlement and splitSettlements are null on an ordinary card payment. merchantId is the same merchant ID the API returns, so a webhook can be reconciled against a query result. feeAmount and netAmount are null until fees are computed.

Retrieving Payment Request Status

The status of a Card request works much like Pay By Bank.

To determine if and how a payment request was completed, we will need to retrieve its status.

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.

Requesting Card Refunds

To reverse any funds back to the cardholder for any successful transaction, see our separately documented Card Refunds API calls.

Test Card Numbers

Our sandbox environment allows simulating successful and failed card transactions, without requiring real debit or credit card details.

You may use the below card numbers on the card input screen to trigger the corresponding scenario.

tip

For all card numbers, any future date for the expiry date (in the format MM/YY), as well as any 3-digit value for the CVC, will process as expected.

ScenarioCard Number
3DS successful and payment completedAny valid card number, such as 4032035421088592
Transaction fails during 3DS verification4004462059871392
Transaction fails during payment authorization4032033425469975
Transaction fails due to insufficient funds4005519200000004
Transaction fails due to exceeding withdrawal limit5284989416854933
Transaction fails due to downstream provider error4787692003020026
Transaction initially has an unknown external state and completes successfully4000056655665556

Postman Collection

Card 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.