Skip to content

Payment Widget Library

A React payment widget for processing payments through the Sawfish API, with hosted card fields and built-in device screening.

The React-based payment form component is available at: npm: sawfish-payment-react-widget

Prerequisites:

  • React 18.2+ / React 19+
  • TypeScript 5.0+
  • Node.js 16.0+
  • Compatible only with React frontends
  • JWT token still required (handled by your backend - refer to Generate Token / Refresh Token for details)
Terminal window
npm install sawfish-payment-react-widget

Add the following variables to your React .env (use the prefix for your framework - NEXT_PUBLIC_ for Next.js, REACT_APP_ for Create React App, VITE_ for Vite):

Terminal window
NEXT_PUBLIC_SAWFISH_ENVIRONMENT=staging
NEXT_PUBLIC_SAWFISH_ENDPOINT=https://api-staging.sawfish.com.au/graphql
NEXT_PUBLIC_SAWFISH_API_SECRET=your-api-secret-here
NEXT_PUBLIC_SAWFISH_CLIENT_ID=your-client-id-here

The widget runs in one of three modes via the mode prop:

Mode What it does
payment (default) Captures card details and processes the payment for amount in one step.
card-retrieval Captures and tokenises the card without charging it - onCardAccountCreated hands you the card account, which your backend can charge later via processPaymentRequest using credit_card.card_account_uid. amount is optional in this mode.
payment-card-retrieval Both: processes the payment for amount and returns the tokenised card account for future charges.
import React from "react";
import { PaymentForm } from "sawfish-payment-react-widget";
const App = () => {
const handlePaymentSuccess = (res, device) => {
// device = { deviceId, ipAddress } - captured from the payer's browser
console.log("Success:", res, device);
};
const handlePaymentError = (err) => console.error("Error:", err);
return (
<PaymentForm
environment={process.env.NEXT_PUBLIC_SAWFISH_ENVIRONMENT || "staging"}
endpoint={process.env.NEXT_PUBLIC_SAWFISH_ENDPOINT || ""}
apiSecret={process.env.NEXT_PUBLIC_SAWFISH_API_SECRET || ""}
clientId={process.env.NEXT_PUBLIC_SAWFISH_CLIENT_ID || ""}
amount={100}
contact={{
first_name: "John",
last_name: "Doe",
email: "john@example.com",
country: "Australia",
}}
onPaymentSuccess={handlePaymentSuccess}
onPaymentError={handlePaymentError}
metadata={{ invoice_id: "1234" }}
description="Invoice-1234"
/>
);
};
export default App;

Sample payment response:

{
"amount": 604.72,
"application_fee_amount": 11.08,
"payment_gateway_fee": 11.08,
"payment_at": "2025-03-04 00:00:00",
"status": {
"name": "payment_initiated"
},
"uuid": "payment-uuid-1234"
}

The widget captures a device fingerprint and the payer’s IP in the browser and passes them to your success callbacks as a second argument: { deviceId, ipAddress }. When your backend later charges a tokenised card through processPaymentRequest, pass these through as zai_device_id and ip_address so the payer’s real device and IP are fraud-screened rather than your server’s.

interface Contact {
first_name: string;
last_name: string;
email: string;
country: string;
}
interface PaymentFormProps {
mode?: "payment" | "card-retrieval" | "payment-card-retrieval"; // default "payment"
environment: "production" | "staging";
endpoint: string; // the GraphQL endpoint for the payment gateway
apiSecret: string; // the API token for the payment gateway
clientId: string; // the client id (/app/settings/app-integration)
amount: number; // required except in card-retrieval mode
contact: Contact;
metadata?: any; // echoed back in the payment webhook
description?: string; // the invoice or order id
// Callbacks - device is { deviceId: string, ipAddress: string }
onPaymentSuccess?: (response: any, device?: Device) => void;
onCardAccountCreated?: (response: any, device?: Device) => void; // card-retrieval modes
onPaymentError?: (error: any) => void;
onConfirm?: () => boolean | Promise<boolean>; // veto hook before submitting
// Presentation
title?: string | React.ReactNode;
submitButtonText?: string;
customStyles?: CreditCardFormStyles; // per-element style overrides for the whole form
renderSubmitButtons?: (params: {
handleSubmit: () => void;
isSubmitting: boolean;
isLoading: boolean;
amount?: { value: number; total: number };
}) => React.ReactNode; // replace the submit button entirely
}

CreditCardFormStyles accepts React.CSSProperties overrides for each part of the form - container, labels, inputs, error and alert text, amount display, submit button, and the hosted card fields - so the widget can match your product’s look without custom CSS files.