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)
Installation
Section titled “Installation”npm install sawfish-payment-react-widgetConfiguration
Section titled “Configuration”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):
NEXT_PUBLIC_SAWFISH_ENVIRONMENT=stagingNEXT_PUBLIC_SAWFISH_ENDPOINT=https://api-staging.sawfish.com.au/graphqlNEXT_PUBLIC_SAWFISH_API_SECRET=your-api-secret-hereNEXT_PUBLIC_SAWFISH_CLIENT_ID=your-client-id-hereThe 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. |
Example Usage
Section titled “Example Usage”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"}Device screening
Section titled “Device screening”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.
Interfaces
Section titled “Interfaces”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.

