react-native-fincra-checkout 1.0.0

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.
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import type { WebViewCheckoutConfig, InlineCheckoutConfig, FincraCheckoutResult } from '../types';
3
+ /**
4
+ * @internal
5
+ * Imperative handle for the FincraCheckoutHost ref.
6
+ * Do not call `_openWebView` / `_openInline` directly — use `FincraCheckout.open*()`.
7
+ */
8
+ export interface FincraCheckoutHostHandle {
9
+ _openWebView(config: WebViewCheckoutConfig): Promise<FincraCheckoutResult>;
10
+ _openInline(config: InlineCheckoutConfig): Promise<FincraCheckoutResult>;
11
+ }
12
+ /**
13
+ * Place this component once at your app root (inside your root view, after
14
+ * your navigator/providers). It renders nothing until `FincraCheckout.open*()`
15
+ * is called — then it mounts a full-screen `Modal` over the current UI.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // App.tsx
20
+ * export default function App() {
21
+ * return (
22
+ * <NavigationContainer>
23
+ * <RootNavigator />
24
+ * <FincraCheckoutHost /> {/* ← add this once *\/}
25
+ * </NavigationContainer>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+ export declare const FincraCheckoutHost: React.ForwardRefExoticComponent<React.RefAttributes<FincraCheckoutHostHandle>>;
31
+ /**
32
+ * @internal
33
+ * Called by `<FincraCheckoutHostRegistrar />` to register the singleton ref.
34
+ * Not part of the public API — do not call this directly.
35
+ */
36
+ export declare function _registerHostRef(ref: React.RefObject<FincraCheckoutHostHandle | null>): void;
37
+ /**
38
+ * @internal
39
+ * Clears the singleton ref when the host unmounts.
40
+ * Not part of the public API — do not call this directly.
41
+ */
42
+ export declare function _unregisterHostRef(): void;
43
+ /**
44
+ * Imperative static API for opening Fincra Checkout modals from anywhere
45
+ * in your app — no navigation prop or context required.
46
+ *
47
+ * **Prerequisite**: `<FincraCheckoutHost />` must be mounted at your app root.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * // WebView mode (recommended — backend-generated URL)
52
+ * const result = await FincraCheckout.openWebView({
53
+ * checkoutUrl: 'https://checkout.fincra.com/pay/...',
54
+ * redirectUrl: 'https://api.yourapp.com/payment/callback',
55
+ * });
56
+ *
57
+ * // Inline mode (frontend-initiated)
58
+ * const result = await FincraCheckout.openInline({
59
+ * publicKey: 'pk_live_...',
60
+ * amount: 5000,
61
+ * currency: 'NGN',
62
+ * customerEmail: 'user@example.com',
63
+ * customerName: 'Jane Doe',
64
+ * customerPhoneNumber: '08012345678',
65
+ * feeBearer: 'customer',
66
+ * });
67
+ *
68
+ * switch (result.type) {
69
+ * case 'success': console.log(result.response.reference); break;
70
+ * case 'error': console.error(result.error.message); break;
71
+ * case 'cancelled': console.log('User cancelled'); break;
72
+ * }
73
+ * ```
74
+ */
75
+ export declare class FincraCheckout {
76
+ /**
77
+ * Opens the WebView checkout in a full-screen modal.
78
+ *
79
+ * This is the **recommended** flow — your backend generates the `checkoutUrl`
80
+ * using the Fincra API with your **secret key** (never in the app).
81
+ *
82
+ * @param config - WebView checkout configuration.
83
+ * @returns A promise resolving to a `FincraCheckoutResult` discriminated union.
84
+ * @throws Error if `<FincraCheckoutHost />` is not mounted.
85
+ * @throws Error if a checkout session is already open.
86
+ */
87
+ static openWebView(config: WebViewCheckoutConfig): Promise<FincraCheckoutResult>;
88
+ /**
89
+ * Opens the Inline JavaScript checkout in a full-screen modal.
90
+ *
91
+ * Uses only the Fincra **public key** (`pk_...`).
92
+ * The Fincra JS SDK is loaded from the CDN at runtime.
93
+ *
94
+ * @param config - Inline checkout configuration.
95
+ * @returns A promise resolving to a `FincraCheckoutResult` discriminated union.
96
+ * @throws Error if `<FincraCheckoutHost />` is not mounted.
97
+ * @throws Error if a checkout session is already open.
98
+ */
99
+ static openInline(config: InlineCheckoutConfig): Promise<FincraCheckoutResult>;
100
+ private static _assertHostMounted;
101
+ }
102
+ /**
103
+ * The component you add to your app root.
104
+ * It self-registers as the singleton checkout host.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * // App.tsx
109
+ * import { FincraCheckoutHost } from 'react-native-fincra-checkout';
110
+ *
111
+ * export default function App() {
112
+ * return (
113
+ * <>
114
+ * <YourApp />
115
+ * <FincraCheckoutHost />
116
+ * </>
117
+ * );
118
+ * }
119
+ * ```
120
+ */
121
+ export declare function FincraCheckoutHostRegistrar(): React.JSX.Element;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import type { InlineCheckoutConfig } from '../types';
3
+ /**
4
+ * A transparent WebView that runs the Fincra inline JavaScript SDK.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <FincraInlineCheckout
9
+ * publicKey="pk_live_xxxx"
10
+ * amount={5000}
11
+ * currency="NGN"
12
+ * customerEmail="user@example.com"
13
+ * customerName="John Doe"
14
+ * customerPhoneNumber="08012345678"
15
+ * feeBearer="customer"
16
+ * onSuccess={(res) => console.log(res.reference)}
17
+ * onCancelled={() => navigation.goBack()}
18
+ * />
19
+ * ```
20
+ */
21
+ export declare function FincraInlineCheckout({ headerTitle, headerBackgroundColor, headerTintColor, showCancelConfirmationDialog, loadingComponent, closeIcon, renderError, onSuccess, onFailed, onCancelled, ...paymentConfig }: InlineCheckoutConfig): React.JSX.Element;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { WebViewCheckoutConfig } from '../types';
3
+ export type FincraWebViewCheckoutProps = WebViewCheckoutConfig;
4
+ /**
5
+ * A full-screen WebView that loads a backend-generated Fincra checkout URL.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <FincraWebViewCheckout
10
+ * checkoutUrl="https://checkout.fincra.com/pay/..."
11
+ * redirectUrl="https://your-backend.com/payment/callback"
12
+ * onSuccess={(res) => console.log('Paid:', res.reference)}
13
+ * onFailed={(err) => console.log('Failed:', err.message)}
14
+ * onCancelled={() => navigation.goBack()}
15
+ * />
16
+ * ```
17
+ */
18
+ export declare function FincraWebViewCheckout({ checkoutUrl, redirectUrl, headerTitle, headerBackgroundColor, headerTintColor, showCancelConfirmationDialog, loadingComponent, closeIcon, renderError, onSuccess, onFailed, onCancelled, }: FincraWebViewCheckoutProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ export { FincraCheckout, FincraCheckoutHostRegistrar as FincraCheckoutHost, } from './checkout/FincraCheckout';
2
+ export { FincraWebViewCheckout } from './components/FincraWebViewCheckout';
3
+ export { FincraInlineCheckout } from './components/FincraInlineCheckout';
4
+ export { FincraCurrency, FeeBearer } from './types';
5
+ export type { FincraPaymentResponse, FincraPaymentError, FincraCheckoutResult, BaseCheckoutProps, WebViewCheckoutConfig, InlineCheckoutConfig, } from './types';
@@ -0,0 +1,33 @@
1
+ import type { FincraPaymentResponse } from '../types';
2
+ /** Events that can be posted from the Fincra inline JavaScript SDK. */
3
+ export declare enum FincraBridgeEvent {
4
+ /** The Fincra SDK has loaded and is ready — hide the loader. */
5
+ Ready = "ready",
6
+ /** Payment completed successfully. */
7
+ Success = "success",
8
+ /** The user closed the Fincra checkout modal. */
9
+ Closed = "closed",
10
+ /** The Fincra SDK emitted an error (e.g., load failure). */
11
+ Error = "error",
12
+ /** Unrecognized or malformed event — should be ignored. */
13
+ Unknown = "unknown"
14
+ }
15
+ /** A parsed message posted by the Fincra JS SDK via `postMessage`. */
16
+ export interface FincraBridgeMessage {
17
+ event: FincraBridgeEvent;
18
+ /** Populated only for `success` and `error` events. */
19
+ data?: FincraPaymentResponse | {
20
+ message: string;
21
+ };
22
+ }
23
+ /**
24
+ * Parses a raw `postMessage` JSON string into a `FincraBridgeMessage`.
25
+ *
26
+ * Expected format from the HTML template:
27
+ * ```json
28
+ * { "event": "success", "data": { "reference": "...", ... } }
29
+ * ```
30
+ *
31
+ * On any parse failure, returns an `Unknown` event (mirrors Flutter's catch block).
32
+ */
33
+ export declare function parseMessage(jsonString: string): FincraBridgeMessage;
@@ -0,0 +1,21 @@
1
+ import type { InlineCheckoutConfig } from '../types';
2
+ /**
3
+ * Narrowed config type: only the payment fields needed to generate the HTML.
4
+ * Deliberately excludes UI props (`onSuccess`, `loadingComponent`, etc.) so
5
+ * the generator can never accidentally embed callbacks as JS values.
6
+ *
7
+ * Fix #8: use a Pick instead of the full InlineCheckoutConfig.
8
+ */
9
+ export type InlinePaymentConfig = Pick<InlineCheckoutConfig, 'publicKey' | 'amount' | 'currency' | 'customerName' | 'customerEmail' | 'customerPhoneNumber' | 'feeBearer' | 'reference' | 'paymentMethods'>;
10
+ /**
11
+ * Generates the self-contained HTML page that loads the Fincra inline JS SDK,
12
+ * initializes it with the provided config, and posts lifecycle events back to
13
+ * the React Native app via `window.ReactNativeWebView.postMessage(...)`.
14
+ *
15
+ * This function is **pure** — given the same config it always returns the same
16
+ * string, making it safe to memoize with `useMemo`.
17
+ *
18
+ * @param config - The inline payment configuration (payment fields only).
19
+ * @returns A complete HTML string to be loaded into a WebView.
20
+ */
21
+ export declare function generateInlineHtml(config: InlinePaymentConfig): string;
@@ -0,0 +1,142 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Currencies supported by the Fincra platform.
4
+ * Expands the Flutter enum (ngn, kes, ugx, ghs, zar, xaf, xof) with
5
+ * additional global currencies from the prompt spec.
6
+ */
7
+ export declare const FincraCurrency: {
8
+ readonly NGN: "NGN";
9
+ readonly USD: "USD";
10
+ readonly GBP: "GBP";
11
+ readonly EUR: "EUR";
12
+ readonly GHS: "GHS";
13
+ readonly KES: "KES";
14
+ readonly ZAR: "ZAR";
15
+ readonly UGX: "UGX";
16
+ readonly XAF: "XAF";
17
+ readonly XOF: "XOF";
18
+ };
19
+ export type FincraCurrency = 'NGN' | 'USD' | 'GBP' | 'EUR' | 'GHS' | 'KES' | 'ZAR' | 'UGX' | 'XAF' | 'XOF';
20
+ export declare const FeeBearer: {
21
+ readonly Business: "business";
22
+ readonly Customer: "customer";
23
+ readonly business: "business";
24
+ readonly customer: "customer";
25
+ };
26
+ export type FeeBearer = 'business' | 'customer';
27
+ /** Normalized successful payment response. */
28
+ export interface FincraPaymentResponse {
29
+ /** Merchant/customer reference — mirrors Flutter's `FincraPaymentResponse.reference`. */
30
+ reference: string;
31
+ /** Fincra internal transaction ID. */
32
+ transactionId: string;
33
+ /** Payment status string (e.g., 'success', 'failed'). */
34
+ status: string;
35
+ /** Optional human-readable message from Fincra. */
36
+ message?: string;
37
+ /**
38
+ * Full raw response map. All values are strings.
39
+ * For inline mode, numeric/boolean fields from Fincra (e.g. `amount`) are
40
+ * coerced to strings to match the URL-params format used in WebView mode.
41
+ */
42
+ rawResponse: Record<string, string>;
43
+ }
44
+ /** Structured payment error. */
45
+ export interface FincraPaymentError {
46
+ /** Error code string (e.g., 'timeout', 'cancelled', HTTP status code). */
47
+ code: string;
48
+ /** Human-readable error description. */
49
+ message: string;
50
+ }
51
+ /**
52
+ * All possible outcomes of a Fincra Checkout session.
53
+ * Use `result.type` to discriminate:
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * switch (result.type) {
58
+ * case 'success': handleSuccess(result.response); break;
59
+ * case 'error': handleError(result.error); break;
60
+ * case 'cancelled': handleCancelled(); break;
61
+ * }
62
+ * ```
63
+ */
64
+ export type FincraCheckoutResult = {
65
+ type: 'success';
66
+ response: FincraPaymentResponse;
67
+ } | {
68
+ type: 'error';
69
+ error: FincraPaymentError;
70
+ } | {
71
+ type: 'cancelled';
72
+ };
73
+ /** Shared props across both checkout modes. */
74
+ export interface BaseCheckoutProps {
75
+ /** Called when payment is successfully completed. */
76
+ onSuccess?: (response: FincraPaymentResponse) => void;
77
+ /** Called when the payment fails or encounters an error. */
78
+ onFailed?: (error: FincraPaymentError) => void;
79
+ /** Called when the user cancels/closes the checkout. */
80
+ onCancelled?: () => void;
81
+ /** Title shown in the navigation header. Defaults to 'Secure Checkout'. */
82
+ headerTitle?: string;
83
+ /** Background color of the navigation header bar. */
84
+ headerBackgroundColor?: string;
85
+ /**
86
+ * Text/icon color inside the navigation header.
87
+ * Also determines the status bar style:
88
+ * - `'#000000'` (default) → `dark-content` status bar icons
89
+ * - Any other value → `light-content` status bar icons
90
+ */
91
+ headerTintColor?: string;
92
+ /** If true, shows a confirmation dialog before dismissing. Default: false. */
93
+ showCancelConfirmationDialog?: boolean;
94
+ /** Custom loading indicator to display while the WebView is loading. */
95
+ loadingComponent?: ReactNode;
96
+ /** Custom close icon/element for the header. */
97
+ closeIcon?: ReactNode;
98
+ /** Custom error screen renderer for network/loading recovery. */
99
+ renderError?: (error: {
100
+ code: string;
101
+ message: string;
102
+ }, retry: () => void) => ReactNode;
103
+ }
104
+ /** Configuration for the WebView-based checkout (backend-generated URL). */
105
+ export interface WebViewCheckoutConfig extends BaseCheckoutProps {
106
+ /**
107
+ * The backend-generated Fincra checkout URL.
108
+ * Generate this server-side using the Fincra API with your **secret key**.
109
+ * Never generate this on the client.
110
+ */
111
+ checkoutUrl: string;
112
+ /**
113
+ * Your backend redirect URL. When Fincra navigates to this URL,
114
+ * the SDK intercepts and resolves the payment result.
115
+ * If omitted, the SDK falls back to detecting `status` + `reference` query params.
116
+ */
117
+ redirectUrl?: string;
118
+ }
119
+ /** Configuration for the Inline JavaScript checkout (frontend-initiated). */
120
+ export interface InlineCheckoutConfig extends BaseCheckoutProps {
121
+ /**
122
+ * Your Fincra **public key** (starts with `pk_`).
123
+ * @security Never use your secret key in mobile app code.
124
+ */
125
+ publicKey: string;
126
+ /** Amount to charge in the smallest currency unit (e.g., kobo for NGN). */
127
+ amount: number;
128
+ /** Currency for the transaction. */
129
+ currency: FincraCurrency;
130
+ /** Customer's email address. */
131
+ customerEmail: string;
132
+ /** Customer's full name. */
133
+ customerName: string;
134
+ /** Customer's phone number. */
135
+ customerPhoneNumber: string;
136
+ /** Who bears the Fincra processing fee. */
137
+ feeBearer: FeeBearer;
138
+ /** Optional unique transaction reference. Fincra generates one if omitted. */
139
+ reference?: string;
140
+ /** Restrict checkout to specific payment methods (e.g., ['card', 'bank_transfer']). */
141
+ paymentMethods?: string[];
142
+ }
@@ -0,0 +1,44 @@
1
+ import type { FincraPaymentResponse } from '../types';
2
+ /**
3
+ * Utilities for detecting Fincra payment completion URLs and
4
+ * extracting normalized response parameters.
5
+ */
6
+ export declare class UrlHandler {
7
+ /**
8
+ * Returns `true` if the given URL signals a Fincra payment completion.
9
+ *
10
+ * Logic (mirrors Flutter):
11
+ * 1. If `expectedRedirectUrl` is provided, check `url.startsWith(expectedRedirectUrl)`.
12
+ * 2. Fallback: Fincra appends `status` (or `payment_status`) AND `reference` as query params.
13
+ *
14
+ * @param url - The URL being navigated to.
15
+ * @param expectedRedirectUrl - The redirect URL you registered on your backend.
16
+ */
17
+ static isCompletionUrl(url: string, expectedRedirectUrl?: string): boolean;
18
+ /**
19
+ * Extracts all query parameters from the URL as a `Record<string, string>`.
20
+ *
21
+ * @param url - The completion URL from Fincra.
22
+ */
23
+ static extractResponseParams(url: string): Record<string, string>;
24
+ /**
25
+ * Builds a normalized `FincraPaymentResponse` from URL query parameters.
26
+ *
27
+ * Mirrors `FincraPaymentResponse.fromUrlParams()` in Flutter, including
28
+ * the reference normalization logic (customerReference → merchantReference → reference).
29
+ *
30
+ * @param params - Raw query params extracted from the completion URL.
31
+ */
32
+ static parsePaymentResponse(params: Record<string, string>): FincraPaymentResponse;
33
+ /**
34
+ * Determines if a status string represents a successful payment.
35
+ *
36
+ * @param status - The raw status string from Fincra.
37
+ */
38
+ static isSuccessStatus(status: string): boolean;
39
+ /**
40
+ * Parses URL query string into a `URLSearchParams`-like `Map`.
41
+ * Works in React Native (no DOM `URL` API available).
42
+ */
43
+ private static _parseQueryParams;
44
+ }
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "react-native-fincra-checkout",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready React Native SDK for Fincra Checkout — WebView and Inline JavaScript modes with full TypeScript support.",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/commonjs/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "react-native": "./src/index.ts",
13
+ "import": {
14
+ "types": "./lib/typescript/index.d.ts",
15
+ "default": "./lib/commonjs/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./lib/typescript/index.d.ts",
19
+ "default": "./lib/commonjs/index.js"
20
+ }
21
+ }
22
+ },
23
+ "files": [
24
+ "src",
25
+ "lib",
26
+ "!lib/typescript/example",
27
+ "!**/__tests__",
28
+ "!**/__fixtures__",
29
+ "!**/__mocks__",
30
+ "!**/.*"
31
+ ],
32
+ "scripts": {
33
+ "test": "jest",
34
+ "typecheck": "tsc --noEmit",
35
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
36
+ "build": "tsc --project tsconfig.build.json",
37
+ "prepare": "tsc --project tsconfig.build.json"
38
+ },
39
+ "keywords": [
40
+ "react-native",
41
+ "fincra",
42
+ "checkout",
43
+ "payment",
44
+ "webview",
45
+ "sdk"
46
+ ],
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/fincra/react-native-fincra-checkout.git"
50
+ },
51
+ "author": "Fincra",
52
+ "license": "MIT",
53
+ "bugs": {
54
+ "url": "https://github.com/fincra/react-native-fincra-checkout/issues"
55
+ },
56
+ "homepage": "https://github.com/fincra/react-native-fincra-checkout#readme",
57
+ "peerDependencies": {
58
+ "react": ">=17.0.0",
59
+ "react-native": ">=0.68.0",
60
+ "react-native-safe-area-context": ">=4.0.0",
61
+ "react-native-webview": ">=13.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@babel/core": "^7.24.0",
65
+ "@babel/preset-env": "^7.24.0",
66
+ "@babel/preset-react": "^7.24.0",
67
+ "@babel/preset-typescript": "^7.24.0",
68
+ "@react-native/babel-preset": "^0.73.0",
69
+ "@testing-library/react-native": "^12.4.5",
70
+ "@types/jest": "^29.5.0",
71
+ "@types/react": "^18.0.0",
72
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
73
+ "@typescript-eslint/parser": "^7.0.0",
74
+ "babel-jest": "^29.7.0",
75
+ "eslint": "^8.57.0",
76
+ "eslint-plugin-react": "^7.34.0",
77
+ "eslint-plugin-react-hooks": "^7.1.1",
78
+ "eslint-plugin-react-native": "^4.1.0",
79
+ "jest": "^29.7.0",
80
+ "react": "18.2.0",
81
+ "react-native": "0.73.0",
82
+ "react-native-safe-area-context": "^4.9.0",
83
+ "react-native-webview": "^14.0.1",
84
+ "react-test-renderer": "18.2.0",
85
+ "typescript": "^5.4.0"
86
+ },
87
+ "jest": {
88
+ "preset": "react-native",
89
+ "testEnvironment": "node",
90
+ "moduleFileExtensions": [
91
+ "ts",
92
+ "tsx",
93
+ "js",
94
+ "jsx",
95
+ "json"
96
+ ],
97
+ "testMatch": [
98
+ "**/__tests__/**/*.test.[jt]s?(x)"
99
+ ],
100
+ "transform": {
101
+ "^.+\\.(ts|tsx|js|jsx)$": "babel-jest"
102
+ },
103
+ "transformIgnorePatterns": [
104
+ "node_modules/(?!(react-native|@react-native|react-native-webview|@testing-library|react-native-safe-area-context)/)"
105
+ ],
106
+ "setupFiles": [
107
+ "./__tests__/setup.js"
108
+ ],
109
+ "moduleNameMapper": {
110
+ "^react-native$": "<rootDir>/node_modules/react-native"
111
+ }
112
+ }
113
+ }