@payriff-sdk/react 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Payriff SDK License
2
+
3
+ Copyright (c) 2026 Payriff. All rights reserved.
4
+
5
+ This package (the "Software") is provided by Payriff to let merchants accept payments through
6
+ Payriff's services.
7
+
8
+ 1. You may install and use the Software, and include it unmodified in the build of your own website
9
+ or application, solely to integrate with and accept payments through Payriff.
10
+ 2. You may not modify, sell, sublicense or distribute the Software on its own, publish it under
11
+ another name, or use it with any payment service other than Payriff.
12
+ 3. The Software loads Payriff.js from https://secure.payriff.com. Your use of Payriff's services is
13
+ governed by your agreement with Payriff.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
16
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17
+ NO EVENT SHALL PAYRIFF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR ITS USE.
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # @payriff-sdk/react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ React components for Payriff card payments. Card data stays inside Payriff's secure iframes; your app
4
+ never sees it.
5
+
6
+ ```sh
7
+ npm install @payriff-sdk/react
8
+ ```
9
+
10
+ ## Ready-made card form
11
+
12
+ ```tsx
13
+ import { PayriffProvider, CardForm, usePayriff } from '@payriff-sdk/react';
14
+
15
+ export function Checkout({ orderId, clientSecret }: { orderId: string; clientSecret: string }) {
16
+ return (
17
+ <PayriffProvider publishableKey="pr_pk_live_…" locale="az">
18
+ <CardForm orderId={orderId} clientSecret={clientSecret} layout="row" holder />
19
+ <PayButton />
20
+ </PayriffProvider>
21
+ );
22
+ }
23
+
24
+ function PayButton() {
25
+ const { pay, canPay, paying, error } = usePayriff();
26
+ return (
27
+ <>
28
+ <button disabled={!canPay || paying} onClick={() => pay()}>{paying ? 'Paying…' : 'Pay'}</button>
29
+ {error && <p role="alert">{error.message}</p>}
30
+ </>
31
+ );
32
+ }
33
+ ```
34
+
35
+ `orderId` and `clientSecret` come from your server, which creates the order with your secret key. With 3DS,
36
+ `pay()` sends the customer to the bank, and the bank sends them back to the order's `redirectUrl`.
37
+
38
+ `<CardForm>` props: `layout` (`stacked` | `row` | `grouped`), `holder`, `appearance`
39
+ (`{ theme: 'light' | 'dark' | 'auto', accent, radius, font, labels }`), `className`, `onReady`, `onError`.
40
+
41
+ ## Your own layout
42
+
43
+ ```tsx
44
+ import { PayriffProvider, CardFields, CardNumber, CardExpiry, CardCvv, usePayriff } from '@payriff-sdk/react';
45
+
46
+ <PayriffProvider publishableKey="pr_pk_live_…" theme={{ fontSize: '16px', color: '#111827' }}>
47
+ <CardFields orderId={orderId} clientSecret={clientSecret}>
48
+ <label>Card number</label>
49
+ <CardNumber className="input" />
50
+ <div className="row">
51
+ <CardExpiry className="input" />
52
+ <CardCvv className="input" />
53
+ </div>
54
+ </CardFields>
55
+ <PayButton />
56
+ </PayriffProvider>
57
+ ```
58
+
59
+ Each field is an empty box you style with `className` (border, focus ring via `:focus-within`). The text inside
60
+ follows the provider's `theme`. Show field errors from `usePayriff().errors.number`, `.expiry`, `.cvv`.
61
+
62
+ ## `usePayriff()`
63
+
64
+ | | |
65
+ |---|---|
66
+ | `ready` | The form is mounted and the order is loaded |
67
+ | `canPay` | Ready, the card is complete, and no payment is running |
68
+ | `paying` | A payment is in progress |
69
+ | `error` | The last error, with a `message` you can show, or `null` |
70
+ | `errors` | Error text per field |
71
+ | `order` | The order (`amount`, `currency`, `merchantName`, …) |
72
+ | `pay()` | Pays with the entered card; resolves with the result, or `null` on failure |
73
+ | `payriff` | The underlying Payriff.js instance |
74
+
75
+ ## 3DS return page
76
+
77
+ ```tsx
78
+ import { PayriffProvider, usePaymentResult } from '@payriff-sdk/react';
79
+
80
+ function Result({ orderId, clientSecret }: { orderId: string; clientSecret: string }) {
81
+ const { loading, result, error } = usePaymentResult(orderId, clientSecret);
82
+ if (loading) return <p>Checking your payment…</p>;
83
+ if (error) return <p>{error.message}</p>;
84
+ return <p>Status: {result!.status}</p>;
85
+ }
86
+
87
+ // <PayriffProvider publishableKey="pr_pk_live_…"><Result … /></PayriffProvider>
88
+ ```
89
+
90
+ This is for the customer only. Your server decides whether the order is paid, from `callbackUrl`.
91
+
92
+ ## Notes
93
+
94
+ - **Next.js:** the components are client components (`'use client'` is built in) and render an empty container
95
+ on the server.
96
+ - **Sandbox:** `<PayriffProvider apiBase="https://sb-api.payriff.com" …>` with your sandbox key.
97
+ - **HTTPS:** the card fields only load on HTTPS pages, `https://localhost` included.
98
+ - React 18 or newer. StrictMode is supported.
@@ -0,0 +1,38 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ import { type FormCallbacks } from './useMountedForm.js';
3
+ export interface CardFieldsProps extends FormCallbacks {
4
+ /** From your server, which created the order with the secret key. */
5
+ orderId: string;
6
+ clientSecret: string;
7
+ /** Your own layout, with <CardNumber>, <CardExpiry>, <CardCvv> and optionally <CardHolder> anywhere inside. */
8
+ children: ReactNode;
9
+ }
10
+ /**
11
+ * Card fields in your own layout and styles. Put <CardNumber>, <CardExpiry>, <CardCvv> (and
12
+ * optionally <CardHolder>) anywhere inside; style them with className. Errors are in usePayriff().errors.
13
+ */
14
+ export declare function CardFields({ orderId, clientSecret, children, onReady, onError }: CardFieldsProps): import("react").JSX.Element;
15
+ export interface CardFieldProps {
16
+ className?: string;
17
+ style?: CSSProperties;
18
+ }
19
+ /** The card number field. Style the box with className; the text inside follows PayriffProvider's `theme`. */
20
+ export declare const CardNumber: {
21
+ ({ className, style }: CardFieldProps): import("react").JSX.Element;
22
+ displayName: string;
23
+ };
24
+ /** The expiry date field. */
25
+ export declare const CardExpiry: {
26
+ ({ className, style }: CardFieldProps): import("react").JSX.Element;
27
+ displayName: string;
28
+ };
29
+ /** The CVV field. */
30
+ export declare const CardCvv: {
31
+ ({ className, style }: CardFieldProps): import("react").JSX.Element;
32
+ displayName: string;
33
+ };
34
+ /** The cardholder name field (optional). */
35
+ export declare const CardHolder: {
36
+ ({ className, style }: CardFieldProps): import("react").JSX.Element;
37
+ displayName: string;
38
+ };
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useRef } from 'react';
4
+ import { useMountedForm } from './useMountedForm.js';
5
+ const FieldsContext = createContext(null);
6
+ /**
7
+ * Card fields in your own layout and styles. Put <CardNumber>, <CardExpiry>, <CardCvv> (and
8
+ * optionally <CardHolder>) anywhere inside; style them with className. Errors are in usePayriff().errors.
9
+ */
10
+ export function CardFields({ orderId, clientSecret, children, onReady, onError }) {
11
+ const fields = useRef({});
12
+ const register = useCallback((field, el) => {
13
+ if (el)
14
+ fields.current[field] = el;
15
+ else
16
+ delete fields.current[field];
17
+ }, []);
18
+ useMountedForm('<CardFields>', (payriff) => {
19
+ const { number, expiry, cvv, holder } = fields.current;
20
+ if (!number || !expiry || !cvv) {
21
+ return Promise.reject({ type: 'sdk', code: 'missing_field', message: '<CardFields> needs <CardNumber>, <CardExpiry> and <CardCvv> inside it' });
22
+ }
23
+ return payriff.mount({ orderId, clientSecret, fields: { number, expiry, cvv, holder } });
24
+ }, [orderId, clientSecret], { onReady, onError });
25
+ return _jsx(FieldsContext.Provider, { value: register, children: children });
26
+ }
27
+ function field(name, component) {
28
+ function Field({ className, style }) {
29
+ const register = useContext(FieldsContext);
30
+ if (!register)
31
+ throw new Error(`${component} must be used inside <CardFields>`);
32
+ return _jsx("div", { ref: (el) => register(name, el), className: className, style: style });
33
+ }
34
+ Field.displayName = component.slice(1, -1);
35
+ return Field;
36
+ }
37
+ /** The card number field. Style the box with className; the text inside follows PayriffProvider's `theme`. */
38
+ export const CardNumber = field('number', '<CardNumber>');
39
+ /** The expiry date field. */
40
+ export const CardExpiry = field('expiry', '<CardExpiry>');
41
+ /** The CVV field. */
42
+ export const CardCvv = field('cvv', '<CardCvv>');
43
+ /** The cardholder name field (optional). */
44
+ export const CardHolder = field('holder', '<CardHolder>');
@@ -0,0 +1,21 @@
1
+ import { type CSSProperties } from 'react';
2
+ import type { PayriffAppearance, PayriffCardLayout } from '@payriff-sdk/payriff-js';
3
+ import { type FormCallbacks } from './useMountedForm.js';
4
+ export interface CardFormProps extends FormCallbacks {
5
+ /** From your server, which created the order with the secret key. */
6
+ orderId: string;
7
+ clientSecret: string;
8
+ /** Default `stacked`. */
9
+ layout?: PayriffCardLayout;
10
+ /** Add a cardholder name field. */
11
+ holder?: boolean;
12
+ /** Colours, corner radius, font and labels of the ready-made form. */
13
+ appearance?: PayriffAppearance;
14
+ className?: string;
15
+ style?: CSSProperties;
16
+ }
17
+ /**
18
+ * The ready-made card form: labels, boxes, errors and card brand icon included.
19
+ * Use inside <PayriffProvider>; pay with usePayriff().pay().
20
+ */
21
+ export declare function CardForm({ orderId, clientSecret, layout, holder, appearance, className, style, onReady, onError }: CardFormProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useRef } from 'react';
4
+ import { useMountedForm } from './useMountedForm.js';
5
+ /**
6
+ * The ready-made card form: labels, boxes, errors and card brand icon included.
7
+ * Use inside <PayriffProvider>; pay with usePayriff().pay().
8
+ */
9
+ export function CardForm({ orderId, clientSecret, layout, holder, appearance, className, style, onReady, onError }) {
10
+ const container = useRef(null);
11
+ const appearanceKey = appearance ? JSON.stringify(appearance) : '';
12
+ useMountedForm('<CardForm>', (payriff) => payriff.mountCard(container.current, {
13
+ orderId,
14
+ clientSecret,
15
+ layout,
16
+ holder,
17
+ appearance: appearanceKey ? JSON.parse(appearanceKey) : undefined,
18
+ }), [orderId, clientSecret, layout, holder, appearanceKey], { onReady, onError });
19
+ return _jsx("div", { ref: container, className: className, style: style });
20
+ }
@@ -0,0 +1,62 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PayriffApiBase, PayriffError, PayriffEvents, PayriffField, PayriffInstance, PayriffLocale, PayriffOptions, PayriffOrder, PayriffResult, PayriffTheme } from '@payriff-sdk/payriff-js';
3
+ export interface PayOptions {
4
+ /** Cardholder name collected in your own input, when the form has no holder field. */
5
+ holderValue?: string;
6
+ }
7
+ /** A Payriff error with a `message` you can show as is. */
8
+ export type PayriffReactError = PayriffError & {
9
+ message: string;
10
+ };
11
+ export interface PayriffProviderProps {
12
+ /** Your publishable key (pr_pk_live_…). Never the secret key. */
13
+ publishableKey: string;
14
+ /** Default `https://api.payriff.com`. Use `https://sb-api.payriff.com` for the sandbox. */
15
+ apiBase?: PayriffApiBase;
16
+ /** Default `az`. */
17
+ locale?: PayriffLocale;
18
+ /** Text styling inside the card fields. With `<CardForm>`, prefer its `appearance`. */
19
+ theme?: PayriffTheme;
20
+ /** Move focus number → expiry → CVV automatically. Default `true`. */
21
+ autoAdvance?: boolean;
22
+ children?: ReactNode;
23
+ }
24
+ export interface UsePayriff {
25
+ /** The card form is mounted and the order is loaded. */
26
+ ready: boolean;
27
+ /** Ready, every field is valid, and no payment is running: enable your Pay button on this. */
28
+ canPay: boolean;
29
+ /** A payment is in progress. */
30
+ paying: boolean;
31
+ /** The last error (loading, mounting or paying), or `null`. */
32
+ error: PayriffReactError | null;
33
+ /** Current error text per field, as shown to the customer. */
34
+ errors: Partial<Record<PayriffField, string | null>>;
35
+ /** The order being paid, once loaded. */
36
+ order: PayriffOrder | null;
37
+ /**
38
+ * Pay with the entered card. With 3DS the customer goes to the bank and comes back to your
39
+ * redirectUrl, so the promise doesn't resolve. Resolves with the result when no redirect is
40
+ * needed, or `null` if the payment failed (see `error`).
41
+ */
42
+ pay: (options?: PayOptions) => Promise<PayriffResult | null>;
43
+ /** The underlying Payriff.js instance, for anything not covered here. */
44
+ payriff: PayriffInstance | null;
45
+ }
46
+ /** What a mounted form reports to the provider. Internal. */
47
+ export interface FormLink {
48
+ options: PayriffOptions;
49
+ publishableKey: string;
50
+ attach(instance: PayriffInstance, order: PayriffOrder): void;
51
+ detach(instance: PayriffInstance | null): void;
52
+ change(e: PayriffEvents['change']): void;
53
+ fail(err: unknown): void;
54
+ reset(): void;
55
+ }
56
+ export declare function toReactError(err: unknown): PayriffReactError;
57
+ /** Loads Payriff.js once for everything inside it, and shares the payment state with usePayriff(). */
58
+ export declare function PayriffProvider({ publishableKey, apiBase, locale, theme, autoAdvance, children }: PayriffProviderProps): import("react").JSX.Element;
59
+ /** Payment state and actions for the card form inside the nearest <PayriffProvider>. */
60
+ export declare function usePayriff(): UsePayriff;
61
+ /** @internal Used by <CardForm> and <CardFields>. */
62
+ export declare function useFormLink(component: string): FormLink;
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
4
+ const ProviderContext = createContext(null);
5
+ const FALLBACK_MESSAGES = {
6
+ validation: 'Please check the card details.',
7
+ network_error: 'Network problem. Please try again.',
8
+ rate_limited: 'Too many attempts. Please start a new order.',
9
+ session_expired: 'This payment session has expired. Please start a new order.',
10
+ order_not_payable: 'This order can no longer be paid.',
11
+ invalid_public_key: 'Payments are not available on this site.',
12
+ };
13
+ /** Payriff errors are plain objects with a string `type`. A thrown Error/TypeError is something else. */
14
+ function isPayriffError(err) {
15
+ return !!err && typeof err === 'object' && !(err instanceof Error) && typeof err.type === 'string';
16
+ }
17
+ export function toReactError(err) {
18
+ if (!isPayriffError(err)) {
19
+ // Its text could be anything (e.g. "Payriff is not a function"); keep it out of the customer's view.
20
+ console.error('[payriff] unexpected error:', err);
21
+ return { type: 'sdk', code: 'unexpected', message: 'Something went wrong. Please try again.' };
22
+ }
23
+ const message = ('message' in err && typeof err.message === 'string' && err.message) || FALLBACK_MESSAGES[err.type] || 'Payment failed.';
24
+ return { ...err, message };
25
+ }
26
+ /** Loads Payriff.js once for everything inside it, and shares the payment state with usePayriff(). */
27
+ export function PayriffProvider({ publishableKey, apiBase, locale, theme, autoAdvance, children }) {
28
+ const [payriff, setPayriff] = useState(null);
29
+ const [order, setOrder] = useState(null);
30
+ const [complete, setComplete] = useState(false);
31
+ const [errors, setErrors] = useState({});
32
+ const [paying, setPaying] = useState(false);
33
+ const [error, setError] = useState(null);
34
+ const current = useRef(null);
35
+ // Compare the theme by value, so an inline object doesn't remount the form on every render.
36
+ const themeKey = theme ? JSON.stringify(theme) : '';
37
+ const options = useMemo(() => ({ apiBase, locale, autoAdvance, theme: themeKey ? JSON.parse(themeKey) : undefined }), [apiBase, locale, autoAdvance, themeKey]);
38
+ const link = useMemo(() => ({
39
+ options,
40
+ publishableKey,
41
+ attach(instance, loaded) {
42
+ current.current = instance;
43
+ setPayriff(instance);
44
+ setOrder(loaded);
45
+ },
46
+ detach(instance) {
47
+ if (instance && current.current !== instance)
48
+ return;
49
+ current.current = null;
50
+ setPayriff(null);
51
+ setOrder(null);
52
+ setComplete(false);
53
+ setPaying(false);
54
+ },
55
+ change(e) {
56
+ setComplete(e.complete);
57
+ setErrors((prev) => (prev[e.field] === e.error ? prev : { ...prev, [e.field]: e.error }));
58
+ },
59
+ fail(err) {
60
+ setError(toReactError(err));
61
+ },
62
+ reset() {
63
+ setErrors({});
64
+ setError(null);
65
+ },
66
+ }), [options, publishableKey]);
67
+ const pay = useCallback(async (opts) => {
68
+ const instance = current.current;
69
+ if (!instance)
70
+ return null;
71
+ setPaying(true);
72
+ setError(null);
73
+ try {
74
+ return await instance.confirmCardPayment(opts);
75
+ }
76
+ catch (err) {
77
+ setError(toReactError(err));
78
+ return null;
79
+ }
80
+ finally {
81
+ setPaying(false);
82
+ }
83
+ }, []);
84
+ const state = useMemo(() => {
85
+ const ready = payriff !== null && order !== null;
86
+ return { ready, canPay: ready && complete && !paying, paying, error, errors, order, pay, payriff };
87
+ }, [payriff, order, complete, paying, error, errors, pay]);
88
+ const value = useMemo(() => ({ state, link }), [state, link]);
89
+ return _jsx(ProviderContext.Provider, { value: value, children: children });
90
+ }
91
+ function useProviderContext(hook) {
92
+ const ctx = useContext(ProviderContext);
93
+ if (!ctx)
94
+ throw new Error(`${hook} must be used inside <PayriffProvider>`);
95
+ return ctx;
96
+ }
97
+ /** Payment state and actions for the card form inside the nearest <PayriffProvider>. */
98
+ export function usePayriff() {
99
+ return useProviderContext('usePayriff()').state;
100
+ }
101
+ /** @internal Used by <CardForm> and <CardFields>. */
102
+ export function useFormLink(component) {
103
+ return useProviderContext(component).link;
104
+ }
@@ -0,0 +1,10 @@
1
+ export { PayriffProvider, usePayriff } from './PayriffProvider.js';
2
+ export type { PayriffProviderProps, UsePayriff, PayriffReactError, PayOptions } from './PayriffProvider.js';
3
+ export { CardForm } from './CardForm.js';
4
+ export type { CardFormProps } from './CardForm.js';
5
+ export { CardFields, CardNumber, CardExpiry, CardCvv, CardHolder } from './CardFields.js';
6
+ export type { CardFieldsProps, CardFieldProps } from './CardFields.js';
7
+ export { usePaymentResult } from './usePaymentResult.js';
8
+ export type { UsePaymentResult } from './usePaymentResult.js';
9
+ export type { FormCallbacks } from './useMountedForm.js';
10
+ export type { PayriffAppearance, PayriffCardLayout, PayriffError, PayriffField, PayriffLocale, PayriffOrder, PayriffResult, PayriffTheme, } from '@payriff-sdk/payriff-js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ export { PayriffProvider, usePayriff } from './PayriffProvider.js';
3
+ export { CardForm } from './CardForm.js';
4
+ export { CardFields, CardNumber, CardExpiry, CardCvv, CardHolder } from './CardFields.js';
5
+ export { usePaymentResult } from './usePaymentResult.js';
@@ -0,0 +1,13 @@
1
+ import { type PayriffInstance, type PayriffOrder } from '@payriff-sdk/payriff-js';
2
+ import { type PayriffReactError } from './PayriffProvider.js';
3
+ export interface FormCallbacks {
4
+ /** The form is mounted and the order is loaded. */
5
+ onReady?: (order: PayriffOrder) => void;
6
+ /** Loading or mounting failed. Also available as `usePayriff().error`. */
7
+ onError?: (error: PayriffReactError) => void;
8
+ }
9
+ /**
10
+ * Creates a Payriff.js instance, mounts with `mount`, and unmounts on cleanup or when `deps` change.
11
+ * A new instance per mount keeps React StrictMode's mount → unmount → mount clean.
12
+ */
13
+ export declare function useMountedForm(component: string, mount: (payriff: PayriffInstance) => Promise<PayriffOrder>, deps: unknown[], { onReady, onError }: FormCallbacks): void;
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { loadPayriff } from '@payriff-sdk/payriff-js';
4
+ import { toReactError, useFormLink } from './PayriffProvider.js';
5
+ /**
6
+ * Creates a Payriff.js instance, mounts with `mount`, and unmounts on cleanup or when `deps` change.
7
+ * A new instance per mount keeps React StrictMode's mount → unmount → mount clean.
8
+ */
9
+ export function useMountedForm(component, mount, deps, { onReady, onError }) {
10
+ const link = useFormLink(component);
11
+ // Latest callbacks without remounting when the parent passes new arrow functions.
12
+ const latest = useRef({ mount, onReady, onError });
13
+ latest.current = { mount, onReady, onError };
14
+ useEffect(() => {
15
+ let cancelled = false;
16
+ let instance = null;
17
+ link.reset();
18
+ const fail = (err) => {
19
+ if (cancelled)
20
+ return;
21
+ const error = toReactError(err);
22
+ link.fail(error);
23
+ latest.current.onError?.(error);
24
+ };
25
+ loadPayriff(link.publishableKey, link.options).then(async (payriff) => {
26
+ if (cancelled || !payriff)
27
+ return; // unmounted already, or server rendering
28
+ instance = payriff;
29
+ payriff.on('change', (e) => link.change(e));
30
+ try {
31
+ const order = await latest.current.mount(payriff);
32
+ if (cancelled)
33
+ return;
34
+ link.attach(payriff, order);
35
+ latest.current.onReady?.(order);
36
+ }
37
+ catch (err) {
38
+ if (err?.code !== 'unmounted')
39
+ fail(err);
40
+ }
41
+ }, fail);
42
+ return () => {
43
+ cancelled = true;
44
+ instance?.unmount();
45
+ link.detach(instance);
46
+ };
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [link, ...deps]);
49
+ }
@@ -0,0 +1,14 @@
1
+ import { type PayriffResult } from '@payriff-sdk/payriff-js';
2
+ import { type PayriffReactError } from './PayriffProvider.js';
3
+ export interface UsePaymentResult {
4
+ /** Still waiting for a final status. */
5
+ loading: boolean;
6
+ /** `{ status, orderId, … }`, or `{ status: 'PENDING', timedOut: true }` if it took too long. */
7
+ result: PayriffResult | null;
8
+ error: PayriffReactError | null;
9
+ }
10
+ /**
11
+ * For your 3DS return page (redirectUrl): waits for the order's final status.
12
+ * Shows the customer the outcome only; your server confirms the payment via callbackUrl.
13
+ */
14
+ export declare function usePaymentResult(orderId: string | null | undefined, clientSecret: string | null | undefined): UsePaymentResult;
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import { loadPayriff } from '@payriff-sdk/payriff-js';
4
+ import { toReactError, useFormLink } from './PayriffProvider.js';
5
+ /**
6
+ * For your 3DS return page (redirectUrl): waits for the order's final status.
7
+ * Shows the customer the outcome only; your server confirms the payment via callbackUrl.
8
+ */
9
+ export function usePaymentResult(orderId, clientSecret) {
10
+ const link = useFormLink('usePaymentResult()');
11
+ const [state, setState] = useState({ loading: true, result: null, error: null });
12
+ useEffect(() => {
13
+ if (!orderId || !clientSecret) {
14
+ setState({ loading: false, result: null, error: toReactError({ type: 'sdk', code: 'missing_order', message: 'No order to check' }) });
15
+ return;
16
+ }
17
+ let cancelled = false;
18
+ setState({ loading: true, result: null, error: null });
19
+ loadPayriff(link.publishableKey, link.options)
20
+ .then((payriff) => payriff?.waitForResult(orderId, clientSecret))
21
+ .then((result) => { if (!cancelled && result)
22
+ setState({ loading: false, result, error: null }); }, (err) => { if (!cancelled)
23
+ setState({ loading: false, result: null, error: toReactError(err) }); });
24
+ return () => { cancelled = true; };
25
+ }, [link, orderId, clientSecret]);
26
+ return state;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@payriff-sdk/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "React components for Payriff.js card payments",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": ["dist", "README.md", "LICENSE"],
17
+ "sideEffects": false,
18
+ "keywords": ["payriff", "payments", "card", "3ds", "react"],
19
+ "scripts": {
20
+ "build": "rm -rf dist && tsc -p tsconfig.json",
21
+ "test": "npm run build && node --test test/*.test.js",
22
+ "prepublishOnly": "npm test"
23
+ },
24
+ "dependencies": {
25
+ "@payriff-sdk/payriff-js": "^1.2.2"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "19.3.0",
32
+ "react": "19.3.1",
33
+ "react-dom": "19.3.1",
34
+ "typescript": "5.6.3"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }