Web Application Integration Process
This integration guide pertains to applications built using web-based frameworks. Stitch provides a JavaScript client-side SDK for accepting wallet payments. Stitch also vends framework-agnostic Web and React components to support quickly adding wallet payment buttons to your interface. The component supports customisation within the brand guidelines stipulated by Apple Pay, Samsung Pay and Google Pay™.
To offer all supported wallet providers, the aforementioned configuration must be completed and the payment methods must be enabled on your Stitch client.
Client
| Description | Package | Language/Framework |
|---|---|---|
| Stitch SDK | @stitch-money/client | JavaScript |
| React Components | @stitch-money/react | ReactJS |
| Web Components | @stitch-money/web | Web/Angular |
Component Interactions
This sequence diagram outlines the high-level client and server-side component interactions for Apple Pay, Samsung Pay and Google Pay. The component interactions are generalised across providers, but note that the flows differ subtly. For example, there is no merchant verification step required for Google Pay.
Client-side Integration
SDK Integration
The integration can be sub-divided into three sections: installing the SDK, handling supported payment methods, and creating a payment button.
Installation
Run the following command to install the SDK and component package applicable to your client-side framework. The @stitch-money/web package should be used for non-React applications.
- React
- Angular
- JS
npm install @stitch-money/client @stitch-money/react
npm install @stitch-money/client @stitch-money/web
npm install @stitch-money/client @stitch-money/web
Ensure that the applicable JavaScript SDK is included in the web application.
- Apple Pay
- Google Pay
- Samsung Pay
<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<script src="https://pay.google.com/gp/p/js/pay.js"></script>
<script src="https://img.mpay.samsung.com/gsmpi/sdk/samsungpay_web_sdk.js"></script>
Import and initialise the SDK using the clientId. Note that the gatewayMerchantId is required for Google Pay.
import { StitchWalletClientSdk } from '@stitch-money/client';
const stitchWalletClient = new StitchWalletClientSdk({
clientId: 'stitch-client-id', // Required
gatewayMerchantId: 'stitch-gateway-merchant-id', // Required if integrating Google Pay
});
Supported Payment Methods
Call supportedPaymentMethods to fetch a list of supported wallets on the device and/or browser. The PayWithWalletButton component can be used to render a payment button for any supported wallet. The method returns an array of WalletType, where the inclusion of an enum value indicates that the provider is applicable to the device/browser.
supportedPaymentMethods should be used to inform the set of payment buttons to surface to the user in your application. For example, if supportedPaymentMethods returns ["SAMSUNG_PAY", "GOOGLE_PAY"] for an Android device with a card provisioned in both the Samsung Wallet and Google Pay apps, then you may choose to display one or both payment buttons using the pre-built component.
Creating Payment Button
Define callback functions for each wallet type to verify a payment session and create a payment. These must be supplied as arguments (along with related payment information) in the onClick handler functions of the SDK.
- A
verifycallback is only required for Apple Pay. - A
createcallback is required for each of Apple Pay, Google Pay and Samsung Pay.
The create callback receives two arguments:
paymentToken: A payment token from the wallet provider, to be base64-encoded before providing to the server-sideinitiateTransactionAPI call. The Apple Pay and Samsung Pay payment tokens are JavaScript objects, while this is a serialised object for Google Pay.- Apple Pay: Encode the
paymentToken.paymentDataobject. - Samsung Pay and Google Pay: Encode the complete
paymentTokenobject.
- Apple Pay: Encode the
fingerprint: A base64-encoded string containing the user's device fingerprint.
The corresponding encoded token and fingerprint values must be provided when initiating the transaction via the Stitch API from your server.
Bind each PayWithWalletButton component (or wrapped web component) onclick event with the corresponding onClick handler function exported from the client-side SDK, e.g., onClickApplePaySessionHandler(...). Instantiate each handler function with the set of required and optional arguments.
See the Component Implementation section below for representative implementations of each wallet provider.
Both verify and create callback functions are required.
The validationUrl is automatically retrieved within onClickApplePaySessionHandler once an Apple Pay web session has been requested (via user interaction with the payment button).
Only a create callback function is required.
The initiativeContext that should be supplied to the onClickSamsungPaySessionHandler is the fully qualified domain name where the request is originated (web application), and must match be one of the domains provided to Stitch as part of merchant configuration.
Only a create callback function is required.
Only information related to the display of the payment sheet is required - No billing or address information needs to be supplied.
Component Implementation
- React
- Angular
- JavaScript
Import the PayWithWalletButton component and styles directly from the @stitch-money/react package to create the payment button.
import {
type PayWithWalletButtonProps,
PayWithWalletButton,
} from "@stitch-money/react";
import "@stitch-money/react/build/styles/css";
For an Angular application, you will first need to create a standalone component that can be composed into your application. The web component exported from @stitch-money/web is a modular HTML element that should be used to define the template for the component.
Use the apple-pay-enabled, samsung-pay-enabled and google-pay-enabled boolean attributes to create buttons for Apple, Samsung and Google Pay respectively.
The stitch-gateway-merchant-id and google-pay-merchant-id attributes must be specified to create a Google Pay payment button using the web component. These correspond to your Stitch-supplied merchant identifier and Google merchant ID received post-registration.
The @Input properties in the component class are not required i.e. do not need to be supplied by a parent component. These values can be defined directly in the standalone component and/or template file.
- Apple Pay
- Samsung Pay
- Google Pay
<!-- apple-pay-button.component.html -->
<stitch-pay-with-wallet
[attr.client-id]="clientId"
[attr.merchant-name]="merchantName"
[total]="total"
apple-pay-enabled
[attr.apple-pay-button-type]="applePayButtonType"
[attr.apple-pay-button-appearance]="applePayButtonAppearance"
(click)="onClickHandler()"
>
</stitch-pay-with-wallet>
// apple-pay-button.component.ts
import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core';
import {
PayWithWallet as PayWithWalletWC,
type ApplePayButtonStyle,
type ApplePayButtonType,
} from '@stitch-money/web/pay-with-wallet';
console.assert(PayWithWalletWC !== undefined);
@Component({
selector: 'apple-pay-wallet-button',
templateUrl: './apple-pay-button.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true,
})
export class ApplePayButtonComponent {
@Input() clientId?: string;
@Input() merchantName?: string;
@Input() applePayButtonType?: ApplePayButtonType;
@Input() applePayButtonAppearance?: ApplePayButtonStyle;
@Input() total?: string;
onClickHandler () {
// Define handler logic
}
}
<!-- samsung-pay-button.component.html -->
<stitch-pay-with-wallet
[attr.client-id]="clientId"
[attr.merchant-name]="merchantName"
[total]="total"
samsung-pay-enabled
(click)="onClickHandler()"
>
</stitch-pay-with-wallet>
// samsung-pay-button.component.ts
import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core';
import {
PayWithWallet as PayWithWalletWC
} from '@stitch-money/web/pay-with-wallet';
console.assert(PayWithWalletWC !== undefined);
@Component({
selector: 'samsung-pay-wallet-button',
templateUrl: './samsung-pay-button.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true,
})
export class SamsungPayButtonComponent {
@Input() clientId?: string;
@Input() merchantName?: string;
@Input() total?: string;
onClickHandler () {
// Define handler logic
}
}
<!-- google-pay-button.component.html -->
<stitch-pay-with-wallet
[attr.client-id]="clientId"
[attr.stitch-gateway-merchant-id]="gatewayMerchantId"
[attr.google-merchant-id]="googlePayMerchantId"
[attr.merchant-name]="merchantName"
[total]="total"
google-pay-enabled
[attr.google-pay-button-type]="googlePayButtonType"
[attr.google-pay-button-appearance]="googlePayButtonAppearance"
(googlepaybuttonclick)="onClickHandler($event)"
>
</stitch-pay-with-wallet>
// google-pay-button.component.ts
import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core';
import {
GooglePayButtonClickEvent,
PayWithWallet as PayWithWalletWC,
GooglePayButtonType,
GooglePayButtonColor,
} from '@stitch-money/web/pay-with-wallet';
console.assert(PayWithWalletWC !== undefined);
@Component({
selector: 'google-pay-wallet-button',
templateUrl: './google-pay-button.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true,
})
export class GooglePayButtonComponent {
@Input() googlePayMerchantId?: string;
@Input() gatewayMerchantId?: string;
@Input() merchantName?: string;
@Input() googlePayButtonType?: GooglePayButtonType;
@Input() googlePayButtonAppearance?: GooglePayButtonColor;
@Input() total?: string;
onClickHandler(event: Event) {
const googlePayEvent = event as GooglePayButtonClickEvent;
// Define handler logic
}
}
The web component can be used in any standard JavaScript application by including the custom element in an HTML file and using a script tag to import the module. In this example, it is assumed that the PayWithWallet component is exported in a seperate JavaScript file /src/pay-with-wallet.ts.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Component Example</title>
<link rel="stylesheet" href="./src/index.css" />
<script type="module" src="/src/pay-with-wallet.ts"></script>
</head>
<body>
<style>
stitch-pay-with-wallet {
--button-border-radius: 20px;
}
</style>
<stitch-pay-with-wallet
client-id="clientId"
stitch-gateway-merchant-id="stitchGatewayMerchantId"
google-merchant-id="googleMerchantId"
merchant-name="merchantName"
total="total"
google-pay-enabled
></stitch-pay-with-wallet>
</body>
<script>
const button = document.querySelector('stitch-pay-with-wallet');
button.onClick = () => {
console.log('button clicked!');
};
</script>
</html>
Sample Implementation
While the following example assumes the use of React, the callback function logic is framework-agnostic and the sample code is valid for any integration.
WALLET_MERCHANT_IDENTIFIER is the Stitch-assigned gateway merchant ID. The same ID may be used to process for all three wallets. However, if required, different IDs can be configured per wallet provider.
- Apple Pay
- Samsung Pay
- Google Pay
import {
StitchWalletClientSdk,
WalletType,
type ApplePaySession,
type StitchTransaction,
} from "@stitch-money/client";
import {
PayWithWalletButton,
PayWithWalletButtonProps,
} from "@stitch-money/react";
import "@stitch-money/react/build/styles/css";
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/router";
export interface WalletPayButtonProps {
amount: number;
reference: string;
disabled?: boolean;
}
export default function WalletPayButton({
amount,
reference,
disabled = false,
}: WalletPayButtonProps) {
const router = useRouter();
const [supportedMethods, setSupportedMethods] = useState<WalletType[]>([]);
const [selectedMethod, setSelectedMethod] = useState<WalletType | null>(null);
const stitchWalletClient = new StitchWalletClientSdk({});
useEffect(() => {
async function getSupportedPaymentMethods() {
const result = await stitchWalletClient.supportedPaymentMethods()
setSupportedMethods(result);
}
getSupportedPaymentMethods();
}, []);
const createApplePayPaymentCallback = useCallback(
async (paymentToken: unknown, fingerprint: unknown) => {
const response = await fetch(
`/api/wallet/apple-pay/create`,
{
method: "POST",
body: JSON.stringify({
paymentToken,
amount: {
quantity: amount,
currency: "ZAR",
},
externalReference: reference,
fingerprint,
}),
}
);
const transaction = (await response.json()) as StitchTransaction;
if (transaction.status === "TransactionSuccess") {
router.push("/success");
}
return { status: transaction.status };
},
[amount, router, selectedMethod]
);
const applePayVerifyCallback = useCallback(async (url: string) => {
const response = await fetch("/api/wallet/apple-pay/verify", {
method: "POST",
body: JSON.stringify({
validationUrl: url,
initiativeContext: window.location.hostname,
}),
});
const session = (await response.json()) as Promise<ApplePaySession>;
return session;
}, []);
const onClickApplePay = useCallback<
PayWithWalletButtonProps["onClick"]
>(() => {
setSelectedMethod(WalletType.ApplePay);
const applePaySessionHandler =
stitchWalletClient.onClickApplePaySessionHandler(
"ZAR",
amount,
"Stitch",
applePayVerifyCallback,
createApplePayPaymentCallback
);
try {
applePaySessionHandler();
} catch (error) {
console.error(`Error starting Apple Pay session: ${error}`);
}
}, [amount, applePayVerifyCallback, createApplePayPaymentCallback]);
if (supportedMethods.includes(WalletType.ApplePay)) {
return (
<>
<PayWithWalletButton
supportedMethods={[WalletType.ApplePay]}
onClick={onClickApplePay}
disabled={disabled}
total={`${amount}`}
/>
</>
);
}
}
import {
StitchWalletClientSdk,
WalletType,
type SamsungPaySession,
type StitchTransaction,
} from "@stitch-money/client";
import {
PayWithWalletButton,
PayWithWalletButtonProps,
} from "@stitch-money/react";
import "@stitch-money/react/build/styles/css";
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/router";
export interface WalletPayButtonProps {
amount: number;
reference: string;
disabled?: boolean;
}
export default function WalletPayButton({
amount,
reference,
disabled = false,
}: WalletPayButtonProps) {
const router = useRouter();
const [supportedMethods, setSupportedMethods] = useState<WalletType[]>([]);
const [selectedMethod, setSelectedMethod] = useState<WalletType | null>(null);
const stitchWalletClient = new StitchWalletClientSdk({});
useEffect(() => {
async function getSupportedPaymentMethods() {
const result = await stitchWalletClient.supportedPaymentMethods()
setSupportedMethods(result);
}
getSupportedPaymentMethods();
}, []);
const createSamsungPayPaymentCallback = useCallback(
async (paymentToken: unknown, fingerprint: unknown) => {
const response = await fetch(
`/api/wallet/samsung-pay/create`,
{
method: "POST",
body: JSON.stringify({
paymentToken,
amount: {
quantity: amount,
currency: "ZAR",
},
externalReference: reference,
fingerprint,
}),
}
);
const transaction = (await response.json()) as StitchTransaction;
if (transaction.status === "TransactionSuccess") {
router.push("/success");
}
return { status: transaction.status };
},
[amount, router, selectedMethod]
);
const onClickSamsungPay = useCallback(async () => {
try {
setSelectedMethod(WalletType.SamsungPay);
const samsungPaySessionHandler =
stitchWalletClient.onClickSamsungPaySessionHandler(
"ZAR",
amount,
"Merchant Store", // Label / descriptor for payment sheet
"Order XYZ", // External reference
`${window.location.origin}`, // Your platform / website domain URL
"${WALLET_MERCHANT_IDENTIFIER}", // Your Stitch-assigned wallet merchant ID
createSamsungPayPaymentCallback
);
samsungPaySessionHandler();
} catch (error) {
console.error(`Error starting Samsung Pay session: ${error}`);
}
}, [amount, reference, samsungPayVerifyCallback]);
if (supportedMethods.includes(WalletType.SamsungPay)) {
return (
<>
<PayWithWalletButton
supportedMethods={[WalletType.SamsungPay]}
onClick={onClickSamsungPay}
disabled={disabled}
total={`${amount}`}
/>
</>
);
}
}
import {
StitchWalletClientSdk,
WalletType,
} from "@stitch-money/client";
import {
PayWithWalletButton,
PayWithWalletButtonProps,
} from "@stitch-money/react";
import "@stitch-money/react/build/styles/css";
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/router";
export interface WalletPayButtonProps {
amount: number;
reference: string;
disabled?: boolean;
}
export default function WalletPayButton({
amount,
reference,
disabled = false,
}: WalletPayButtonProps) {
const router = useRouter();
const [supportedMethods, setSupportedMethods] = useState<WalletType[]>([]);
const [selectedMethod, setSelectedMethod] = useState<WalletType | null>(null);
const stitchWalletClient = new StitchWalletClientSdk({});
useEffect(() => {
async function getSupportedPaymentMethods() {
const result = await stitchWalletClient.supportedPaymentMethods()
setSupportedMethods(result);
}
getSupportedPaymentMethods();
}, []);
const createGooglePayPaymentCallback = useCallback(
async (paymentToken: unknown, fingerprint: unknown) => {
const response = await fetch(
`/api/wallet/google-pay/create`,
{
method: "POST",
body: JSON.stringify({
paymentToken,
amount: {
quantity: amount,
currency: "ZAR",
},
externalReference: reference,
fingerprint,
}),
}
);
const transaction = (await response.json()) as StitchTransaction;
if (transaction.status === "TransactionSuccess") {
router.push("/success");
}
return { status: transaction.status };
},
[amount, router, selectedMethod]
);
const onClickGooglePay = useCallback<PayWithWalletButtonProps["onClick"]>(
(parameters) => {
if (parameters?.googlePay) {
setSelectedMethod(WalletType.GooglePay);
const googlePaySessionHandler =
stitchWalletClient.onClickGooglePaySessionHandler(
createGooglePayPaymentCallback
);
try {
googlePaySessionHandler(parameters?.googlePay?.paymentData);
} catch (error) {
console.error(`Error starting Google Pay session: ${error}`);
}
}
},
[createGooglePayPaymentCallback]
);
if (supportedMethods.includes(WalletType.GooglePay)) {
return (
<>
<PayWithWalletButton
supportedMethods={[WalletType.GooglePay]}
googlePayMerchantId={"GOOGLE_PAY_MERCHANT_ID"}
gatewayMerchantId={"WALLET_MERCHANT_IDENTIFIER"}
onClick={onClickGooglePay}
disabled={disabled}
total={`${amount}`}
/>
</>
);
}
}
Amounts specified on the client-side (i.e. within the wallet button) need to be identical to those in the server-side Stitch API transaction requests. If these do not match, and you initiate a transaction for a different amount to what the user authorised, you will receive a failed status with reason invalidTransactionError.
Server-side Integration
The integration with the Stitch API should run on your trusted application server as calls are authenticated using your sensitive Stitch credentials. This guide describes integrating directly via the Stitch API.
The GraphQL API URL https://api.stitch.money/graphql can be used for all wallet requests (whether on test or live clients). Note that a Stitch client token with the scope transaction_initiate is required to validate wallet merchants, or initiate any wallet transactions, via the API.
Validation API
The verify SDK function is only required for Apple Pay, and must encapsulate the validateApplePayMerchant mutation. This mutation must be called in the server-side verification API route. This step is required before a payment can be initiated.
The intitiative field in the ValidateApplePayMerchantInput input object must always be set to web.
The intitiativeContext field in the ValidateApplePayMerchantInput input object refers to the fully qualified domain name where the request is originated (web application) and must match the domain associated with Apple under the merchantIdentifier specified.
For Apple Pay, the sessionData object in the validateApplePayMerchant response must be converted to a JSON object before being returned by the server-side verify API endpoint handler.
Setting the intiativeContext field to test.stitch.money returns mock session data.
Note that this session data cannot be used to verify and instantiate a client-side wallet provider session. This should be used to test server-side calls in isolation. Set this field with the verified domain when testing end-to-end transaction processing.
Transaction Initiation API
To create a wallet payment, a Stitch API initiateTransaction mutation call is required in order to initiate the payment. The mutation must be called in the server-side payment creation API routes for each wallet provider.
The input variable object must be constructed distinctly for each wallet provider to correctly replicate the interaction encapsulated by the create SDK function.
The fingerprint argument supplied to the create payment callback on the client side should be specified in the deviceInformation.fingerprint field.
- Apple Pay
- Samsung Pay
- Google Pay
All payment tokens specified in the initiateTransaction mutation must be base64-encoded values.
- For Apple Pay, supply the encoded value of
paymentToken.paymentData. - For Samsung and Google Pay, supply the encoded value of the complete
paymentToken.
Ensure you initiate a transaction for the same amount as what the user authorised (i.e. presented to the user within the wallet payment button on the client-side). If there is a mismatch between these, you will receive a TransactionFailure status with reason invalidTransactionError.
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
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.
Handling Transaction Responses
The state field contained in the response is a synchronous update on the status of a payment, which will typically be TransactionSuccess or TransactionFailure (with a corresponding reason). Note that this status will also be returned as a webhook update, when subscribed to transaction webhooks.
Webhooks should remain the primary source of your update, to maintain consistency when synchronous updates are not successfully received (due to a network failure, for example).
3DS-Enforced Transactions
Based on agreed-upon configuration with Stitch, 3DS may be enforced per-transaction, on a dynamic basis. In this case, a transaction initiation response will return a TransactionPending status, as well as an interactionUrl.
This interactionUrl returned by the API accepts a redirect_uri, to be appended as a query parameter. The user should then be guided to this URL (after their interaction with the wallet payment sheet), for them to complete the required 3DS interaction for their transaction.
Rendering the 3DS Interaction
There are two ways to display the 3DS authentication flow to the user:
- Inside an iframe (recommended): This keeps the user on your checkout page and allows you to control the experience while safely handling errors or timeouts.
- As a full-page redirect: This navigates the user away from your site to the interaction URL and returns them via a redirect, but offers less control and no built-in recovery if something goes wrong.
1. Displaying the 3DS Flow Inside an iframe (Recommended)
Stitch recommends displaying the 3DS interaction inside an iframe, as this keeps the user within your checkout experience and provides safer recovery paths if the issuing bank’s challenge page fails to load or times out.
When using an iframe, the interaction url returned from the Stitch API must be loaded directly as the iframe’s src:
<iframe src="https://3ds.stitch.money/2ce31de7-a30d-4f3b-bfaa-cd06cb30db79" allow="payment *; fullscreen"></iframe>
When the user finishes the 3DS flow, the interaction page running inside the iframe will notify your parent window using a postMessage event.
Listen for the message in your hosting page:
window.addEventListener('message', (event) => {
if (event.data?.type === 'finished') {
const { flow, externalReference, id: stitchId } = event.data;
// e.g. close modal, update UI, confirm transaction, etc.
}
});
The following fields are available on the message event:
| Field | Description |
|---|---|
type | finished if the 3DS flow is complete |
id | The Stitch ID of the transaction |
flow | The 3DS interaction type of the transaction: challenge or frictionless |
externalReference | The external reference of the transaction |
We strongly recommend displaying the 3DS interaction within an overlay modal that sits on top of your checkout page. The modal should include a visible close or cancel button so that users always have a safe way to exit if the issuing bank’s challenge fails or times out. It should present the 3DS page inside its own isolated iframe container to keep the interaction clearly separated from the rest of your UI and it should offer a retry mechanism so users can attempt the authentication again if anything goes wrong. This approach ensures that customers are never left stuck or redirected to a broken page and gives you full control over the overall experience.
As an alternative, you may embed the iframe directly into the body of your page. If you choose this approach, you should still provide a clear retry or recovery option in case the challenge fails and you must ensure that users always have a safe and obvious way to exit the flow. This helps maintain a smooth checkout experience even without a modal.
2. Displaying the 3DS Flow as a full-page redirect
Alternatively, you can redirect the user to the 3DS interaction URL. In this flow, your checkout page is replaced entirely by the 3DS challenge screen and the user completes the authentication directly on that page.
To initiate the flow, you simply take the interaction url returned by the Stitch API and perform a standard browser redirect to it (either by setting window.location.href in your frontend or by submitting a form to it).
To redirect the user back to your site once the 3DS flow is completed, you must supply a redirect_uri parameter during the initial redirect. For example, if you want the user returned to https://example.com/payment, you would append the following query string to the interaction URL: ?redirect_uri=https%3A%2F%2Fexample.com%2Fpayment.
The final URL you redirect the user to should look similar to:
https://3ds.stitch.money/2ce31de7-a30d-4f3b-bfaa-cd06cb30db79?redirect_uri=https%3A%2F%2Fexample.payment
The URL specified as the redirect_uri must be secure i.e. an HTTPS URL.
Once the user has successfully completed the interaction and the payment has been processed, they will be redirected back to your specified redirect_uri with the following query parameters.
| Parameter | Description |
|---|---|
id | The Stitch ID of the transaction |
flow | The 3DS interaction type of the transaction: challenge or frictionless |
externalReference | The external reference of the transaction |
status | The final status of the transaction after the user's interaction: TransactionSuccess or TransactionFailure |
statusReason | The status reason, if applicable to the transaction status |
You will also receive a corresponding transaction webhook to confirm the status of the transaction.
Transaction Statuses
The table below describes the different statuses of a wallet payment:
| Status | Description |
|---|---|
| TransactionPending | A payment is initialised and awaiting user authentication. If an interactionUrl is returned, the user should be directed to this for the transaction to proceed. |
| TransactionFailure | A payment authorised by a user but unsuccessful and the requested funds were not captured. |
| TransactionSuccess | A payment has been successfully made and the requested funds were captured. |
Failure Reasons
The TransactionFailure status indicates that the wallet transaction failed to be initiated, and includes a reason explaining the cause.
Potential failure reasons are detailed below:
| Reason | Description |
|---|---|
| authorizationFailed | The transaction was declined or blocked. |
| authorizationNotFinalised | The transaction could not be processed by the acquirer. |
| blockedByFraudChecks | The transaction was blocked due to fraud checks. |
| downstreamProviderError | The transaction could not be processed due to downstream error. |
| exceedsCardWithdrawalLimit | The transaction was declined due to withdrawal limits exceeded. |
| insufficientFunds | The transaction was declined due to insufficient funds. |
| internalServerError | The transaction could not be processed due to a server error. |
| invalidCardError | The transaction was declined due to an expired card. |
| invalidConfigurationError | The client has invalid or missing configuration. |
| invalidTransactionError | The authorised payment amount (for the payment token) does not match the transaction input amount. |
| tokenDecryptionError | The payment token could not be decrypted. |
Querying Transactions
Transaction by ID
For implementing redundancy checks and logic, it is possible to query transaction statuses and details over the Stitch GraphQL API. Note that querying with the Stitch-provided id is the most performant, and the recommended approach.
Transaction by External Reference
Alternatively, querying by an externalReference is possible, and may be done with a filtering query, as follows:
Use the SamsungPayTransaction or GooglePayTransaction to query fields specific to Samsung Pay or Google Pay wallet payments. As an example, a query for an Apple Pay payment is shown.
Webhook Subscriptions
Wallet payments require that you subscribe to transaction webhooks to receive asynchronous updates on transaction status changes.
If the subscription is successfully created, the body returned by the request will look similar to the sample in the Example Response
tab in the widget above.
The webhook will contain important information regarding the transaction, including the selected wallet provider, the transaction retrieval reference number (RRN) (used for reconcilliation), external reference, and nonce.
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
When subscribed to the transaction webhook filter type, webhook updates will be sent whenever a transaction is updated to either of the following statuses:
TransactionSuccessTransactionFailure
Testing Payment Initiation
To test payment initiation, you may use any device supported by the wallet providers applicable to your selected integration. See the Google Pay integration checklist for a more detailed description of recommended testing. Note that not all test cases/paths are applicable to the gateway integration.
- Payment initiation testing only applies to Stitch test clients.
- A real and valid credit/debit card must be added to either the Apple, Samsung and/or Google Wallet application.
- No funds will be captured when completing a payment with a real card and a transaction initiated by a test client.
Simulating Transaction Statuses
For testing purposes, specific amounts can be used to simulate different scenarios when using a test client. The table below shows the mapping between amounts and transaction statuses:
| Amount | Status | Reason |
|---|---|---|
| 1.01 | FAILURE | insufficientFunds |
| 2.02 | FAILURE | exceedsCardWithdrawalLimit |
| 3.03 | FAILURE | downstreamProviderError |
| 4.04 | FAILURE | authorizationFailed |
| Other | SUCCESS | - |
Test Device Tokens
To simulate successful transaction initiation requests on test clients (as part of your server-side integration phase), specify the token value to be J2V4YW1wbGVQYXltZW50TWV0aG9kVG9rZW4n. This applies regardless of the wallet transaction type being tested.
Test Cards
Testing the integration on a real device may require linking a test card to that device. For each wallet method, this approach is distinct.
- Apple Pay
- Google Pay
- Samsung Pay
A real credit card must be used together with your test Stitch client. As mentioned above when using your test client the card will not actually be charged.
Using a Stitch Test client for Google Pay does not require adding a card to the device used to initiate payment. You will need to add the Google Account on the device to the following Google Group. Clicking the Google Pay button will then display a suite of test cards to choose from for completing a test payment.
Samsung Pay requires a card from your acquirer. Please contact support@stitch.money for more information on this.