react-native-fincra-checkout 1.0.0 → 1.0.2

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.
@@ -5,6 +5,8 @@ import type { FincraPaymentResponse } from '../types';
5
5
  // Direct TypeScript port of flutter_fincra_checkout/lib/src/utils/url_handler.dart
6
6
  // Mirrors UrlHandler.isCompletionUrl() and UrlHandler.extractResponseParams()
7
7
 
8
+ const DEFAULT_PORTS: Record<string, string> = { http: '80', https: '443' };
9
+
8
10
  /**
9
11
  * Utilities for detecting Fincra payment completion URLs and
10
12
  * extracting normalized response parameters.
@@ -14,7 +16,8 @@ export class UrlHandler {
14
16
  * Returns `true` if the given URL signals a Fincra payment completion.
15
17
  *
16
18
  * Logic (mirrors Flutter):
17
- * 1. If `expectedRedirectUrl` is provided, check `url.startsWith(expectedRedirectUrl)`.
19
+ * 1. If `expectedRedirectUrl` is provided, the URL must match it strictly —
20
+ * see {@link UrlHandler.matchesRedirectUrl}.
18
21
  * 2. Fallback: Fincra appends `status` (or `payment_status`) AND `reference` as query params.
19
22
  *
20
23
  * @param url - The URL being navigated to.
@@ -24,7 +27,7 @@ export class UrlHandler {
24
27
  if (!url) return false;
25
28
 
26
29
  if (expectedRedirectUrl && expectedRedirectUrl.length > 0) {
27
- return url.startsWith(expectedRedirectUrl);
30
+ return UrlHandler.matchesRedirectUrl(url, expectedRedirectUrl);
28
31
  }
29
32
 
30
33
  // Fallback: detect via query parameters
@@ -39,6 +42,53 @@ export class UrlHandler {
39
42
  }
40
43
  }
41
44
 
45
+ /**
46
+ * Strictly matches `url` against the expected redirect URL.
47
+ *
48
+ * - Scheme, host (case-insensitive) and port (default ports normalised) must be equal.
49
+ * - The path must equal the expected path or continue it at a `/` boundary
50
+ * (trailing slashes ignored). An empty expected path matches any path.
51
+ * - Query string and fragment are ignored.
52
+ * - Falls back to `startsWith` only if the expected URL cannot be parsed.
53
+ *
54
+ * Prevents lookalike hosts such as `https://google.com.evil.io` matching
55
+ * `https://google.com`.
56
+ */
57
+ static matchesRedirectUrl(url: string, expectedRedirectUrl: string): boolean {
58
+ const expected = UrlHandler._parseUrl(expectedRedirectUrl);
59
+ if (!expected) return url.startsWith(expectedRedirectUrl);
60
+
61
+ const actual = UrlHandler._parseUrl(url);
62
+ if (!actual) return false;
63
+
64
+ if (
65
+ actual.scheme !== expected.scheme ||
66
+ actual.host !== expected.host ||
67
+ actual.port !== expected.port
68
+ ) {
69
+ return false;
70
+ }
71
+
72
+ if (expected.path === '') return true;
73
+ return (
74
+ actual.path === expected.path ||
75
+ actual.path.startsWith(`${expected.path}/`)
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Reads the payment status from completion params, accepting either
81
+ * `status` or `payment_status`. Returns lower-case.
82
+ *
83
+ * A missing status is treated as `'success'` because the sandbox redirect
84
+ * omits it. **Always verify the payment on your backend** (Fincra API or
85
+ * webhook) before fulfilling an order.
86
+ */
87
+ static extractStatus(params: Record<string, string>): string {
88
+ const raw = params['status'] ?? params['payment_status'];
89
+ return raw?.toLowerCase() ?? 'success';
90
+ }
91
+
42
92
  /**
43
93
  * Extracts all query parameters from the URL as a `Record<string, string>`.
44
94
  *
@@ -81,7 +131,7 @@ export class UrlHandler {
81
131
  return {
82
132
  reference: finalRef,
83
133
  transactionId: finalTxId ?? '',
84
- status: params['status'] ?? 'unknown',
134
+ status: params['status'] ?? params['payment_status'] ?? 'unknown',
85
135
  message: params['message'],
86
136
  rawResponse: params,
87
137
  };
@@ -99,6 +149,29 @@ export class UrlHandler {
99
149
 
100
150
  // ── Internal ────────────────────────────────────────────────────────────────
101
151
 
152
+ /**
153
+ * Minimal absolute-URL parser. React Native's `URL` polyfill does not
154
+ * implement `hostname`/`port`, so this is done by hand.
155
+ * Returns `null` if the string is not an absolute `scheme://host` URL.
156
+ */
157
+ private static _parseUrl(
158
+ url: string
159
+ ): { scheme: string; host: string; port: string; path: string } | null {
160
+ const match =
161
+ /^([a-z][a-z0-9+.-]*):\/\/(?:[^@/?#]*@)?(\[[^\]]*\]|[^:/?#]*)(?::(\d*))?([^?#]*)/i.exec(
162
+ url.trim()
163
+ );
164
+ if (!match || !match[2]) return null;
165
+
166
+ const scheme = match[1].toLowerCase();
167
+ const defaultPort = DEFAULT_PORTS[scheme] ?? '';
168
+ const port = match[3] || defaultPort;
169
+ // Normalise trailing slashes so `/callback/` equals `/callback`
170
+ const path = match[4].replace(/\/+$/, '');
171
+
172
+ return { scheme, host: match[2].toLowerCase(), port, path };
173
+ }
174
+
102
175
  /**
103
176
  * Parses URL query string into a `URLSearchParams`-like `Map`.
104
177
  * Works in React Native (no DOM `URL` API available).