@inno-isw/react-native-rtp 0.1.0 → 0.2.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.
Files changed (39) hide show
  1. package/README.md +33 -10
  2. package/lib/module/client.js +184 -11
  3. package/lib/module/client.js.map +1 -1
  4. package/lib/module/constants.js +5 -0
  5. package/lib/module/constants.js.map +1 -1
  6. package/lib/module/index.js +2 -2
  7. package/lib/module/index.js.map +1 -1
  8. package/lib/module/mock/makeRtpPayload.js +3 -0
  9. package/lib/module/mock/makeRtpPayload.js.map +1 -1
  10. package/lib/module/mock/mockTransport.js +9 -2
  11. package/lib/module/mock/mockTransport.js.map +1 -1
  12. package/lib/module/react/RtpProvider.js +13 -3
  13. package/lib/module/react/RtpProvider.js.map +1 -1
  14. package/lib/module/validate.js +42 -0
  15. package/lib/module/validate.js.map +1 -1
  16. package/lib/typescript/src/client.d.ts +12 -3
  17. package/lib/typescript/src/client.d.ts.map +1 -1
  18. package/lib/typescript/src/constants.d.ts +5 -0
  19. package/lib/typescript/src/constants.d.ts.map +1 -1
  20. package/lib/typescript/src/index.d.ts +2 -2
  21. package/lib/typescript/src/index.d.ts.map +1 -1
  22. package/lib/typescript/src/mock/makeRtpPayload.d.ts.map +1 -1
  23. package/lib/typescript/src/mock/mockTransport.d.ts +7 -1
  24. package/lib/typescript/src/mock/mockTransport.d.ts.map +1 -1
  25. package/lib/typescript/src/react/RtpProvider.d.ts +6 -2
  26. package/lib/typescript/src/react/RtpProvider.d.ts.map +1 -1
  27. package/lib/typescript/src/types.d.ts +11 -0
  28. package/lib/typescript/src/types.d.ts.map +1 -1
  29. package/lib/typescript/src/validate.d.ts +11 -0
  30. package/lib/typescript/src/validate.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/client.ts +202 -13
  33. package/src/constants.ts +5 -0
  34. package/src/index.tsx +3 -1
  35. package/src/mock/makeRtpPayload.ts +3 -0
  36. package/src/mock/mockTransport.ts +18 -2
  37. package/src/react/RtpProvider.tsx +40 -5
  38. package/src/types.ts +11 -0
  39. package/src/validate.ts +48 -0
package/src/index.tsx CHANGED
@@ -1,11 +1,13 @@
1
1
  // Core (platform-agnostic)
2
- export { createRtpClient } from './client';
2
+ export { createRtpClient, normalizeBackendStatus } from './client';
3
3
  export { RTP_STATUS, RTP_EVENT, RTP_ERROR } from './constants';
4
4
  export type { RtpStatus, RtpEvent, RtpError } from './constants';
5
5
  export {
6
6
  isRtpPayload,
7
7
  findValidationErrors,
8
8
  toRtpRequest,
9
+ toRequestPatch,
10
+ mergeRequest,
9
11
  isExpired,
10
12
  } from './validate';
11
13
  export { mapPushToRtp } from './push';
@@ -41,6 +41,9 @@ export function makeRtpPayload(
41
41
  currency: 'NGN',
42
42
  narration: 'Payment for invoice 1042',
43
43
  transactionRef: `TRX-${from}-${counter}`,
44
+ orderNumber: `ORD-${from}-${counter}`,
45
+ deepLink: `quickteller://rtp/${from}-${counter}`,
46
+ actionMessage: 'Review and approve this payment request',
44
47
  recipient: 'john.doe@quickteller.com',
45
48
  expiresAt: new Date(from + expiresInMs).toISOString(),
46
49
  payer: {
@@ -7,8 +7,14 @@ export interface MockTransportOptions {
7
7
  failAccept?: boolean;
8
8
  /** Simulate a network failure on decline. */
9
9
  failDecline?: boolean;
10
+ /** Simulate a network failure on retrieve (details fetch). */
11
+ failRetrieve?: boolean;
10
12
  /** Backend status to return for accept. */
11
- acceptStatus?: 'ACCEPTED' | 'DECLINED' | 'FAILED';
13
+ acceptStatus?: 'ACCEPTED' | 'DECLINED' | 'FAILED' | 'PENDING' | 'EXPIRED';
14
+ /** Backend status to return for retrieve. Unknown values fall through to READY. */
15
+ retrieveStatus?: string;
16
+ /** Request details to return from retrieve (overlaid onto the held request). */
17
+ retrieveRequest?: Record<string, unknown>;
12
18
  }
13
19
 
14
20
  export interface RecordedCall {
@@ -41,7 +47,10 @@ export function createMockTransport(
41
47
  latencyMs = 250,
42
48
  failAccept = false,
43
49
  failDecline = false,
50
+ failRetrieve = false,
44
51
  acceptStatus = 'ACCEPTED',
52
+ retrieveStatus = 'READY',
53
+ retrieveRequest,
45
54
  } = options;
46
55
 
47
56
  const calls: RecordedCall[] = [];
@@ -86,9 +95,16 @@ export function createMockTransport(
86
95
  ): Promise<RtpHttpResponse<T>> {
87
96
  calls.push({ method: 'get', path, headers });
88
97
  await delay();
98
+ if (failRetrieve) {
99
+ throw new Error('Mock network failure on retrieve');
100
+ }
89
101
  return {
90
102
  status: 200,
91
- data: { id: extractId(path), status: 'PENDING' } as T,
103
+ data: {
104
+ id: extractId(path),
105
+ status: retrieveStatus,
106
+ request: retrieveRequest,
107
+ } as T,
92
108
  };
93
109
  },
94
110
  };
@@ -7,7 +7,13 @@ import React, {
7
7
  useRef,
8
8
  useState,
9
9
  } from 'react';
10
- import type { RtpClient, RtpCredential, RtpRequest, RtpResult } from '../types';
10
+ import type {
11
+ RtpClient,
12
+ RtpCredential,
13
+ RtpReceiveOutcome,
14
+ RtpRequest,
15
+ RtpResult,
16
+ } from '../types';
11
17
  import { RTP_STATUS } from '../constants';
12
18
  import type { RtpStatus } from '../constants';
13
19
 
@@ -46,14 +52,18 @@ export interface RtpContextValue {
46
52
  status: RtpStatus | null;
47
53
  /** True when a request is ready for the user to act on. */
48
54
  isReady: boolean;
55
+ /** True while authorization details are being fetched from the backend. */
56
+ isLoading: boolean;
49
57
  /** Accept the active request with an opaque credential. */
50
58
  accept: (credential?: RtpCredential) => Promise<RtpResult | undefined>;
51
59
  /** Decline the active request. */
52
60
  decline: (reason?: string) => Promise<RtpResult | undefined>;
61
+ /** Fetch/refresh authorization details for the active request. */
62
+ retrieve: (id?: string) => Promise<RtpResult | undefined>;
53
63
  /** Dismiss the active request from the UI. */
54
64
  dismiss: () => void;
55
65
  /** Feed a raw decoded payload into the client (for host push adapters). */
56
- receive: (payload: unknown) => void;
66
+ receive: (payload: unknown) => Promise<RtpReceiveOutcome | undefined>;
57
67
  /** Last terminal result, for rendering feedback. */
58
68
  lastResult: RtpResult | null;
59
69
  }
@@ -79,9 +89,9 @@ export function RtpProvider({
79
89
  const activeIdRef = useRef<string | null>(null);
80
90
 
81
91
  const receive = useCallback(
82
- (payload: unknown) => {
92
+ async (payload: unknown) => {
83
93
  onIncomingPayload?.(payload);
84
- client.receive(payload);
94
+ return client.receive(payload);
85
95
  },
86
96
  [client, onIncomingPayload]
87
97
  );
@@ -160,6 +170,17 @@ export function RtpProvider({
160
170
  [client, request]
161
171
  );
162
172
 
173
+ const retrieve = useCallback(
174
+ async (id?: string) => {
175
+ const target = id ?? request?.id ?? activeIdRef.current;
176
+ if (!target) {
177
+ return undefined;
178
+ }
179
+ return client.retrieve(target);
180
+ },
181
+ [client, request]
182
+ );
183
+
163
184
  const dismiss = useCallback(() => {
164
185
  setRequest(null);
165
186
  setStatus(null);
@@ -168,19 +189,33 @@ export function RtpProvider({
168
189
  }, []);
169
190
 
170
191
  const isReady = status === RTP_STATUS.READY;
192
+ const isLoading = status === RTP_STATUS.RETRIEVING;
171
193
 
172
194
  const value = useMemo<RtpContextValue>(
173
195
  () => ({
174
196
  request,
175
197
  status,
176
198
  isReady,
199
+ isLoading,
177
200
  accept,
178
201
  decline,
202
+ retrieve,
179
203
  dismiss,
180
204
  receive,
181
205
  lastResult,
182
206
  }),
183
- [request, status, isReady, accept, decline, dismiss, receive, lastResult]
207
+ [
208
+ request,
209
+ status,
210
+ isReady,
211
+ isLoading,
212
+ accept,
213
+ decline,
214
+ retrieve,
215
+ dismiss,
216
+ receive,
217
+ lastResult,
218
+ ]
184
219
  );
185
220
 
186
221
  return <RtpContext.Provider value={value}>{children}</RtpContext.Provider>;
package/src/types.ts CHANGED
@@ -27,6 +27,12 @@ export interface RtpPayload {
27
27
  narration?: string;
28
28
  /** Backend reference for the transaction. */
29
29
  transactionRef?: string;
30
+ /** Merchant/issuer order number for the request (distinct from transactionRef). */
31
+ orderNumber?: string;
32
+ /** Secure transaction / deep-link reference the host can use to open the flow. */
33
+ deepLink?: string;
34
+ /** Instruction shown to the customer describing the action required. */
35
+ actionMessage?: string;
30
36
  /** ISO 8601 timestamp after which the request can no longer be actioned. */
31
37
  expiresAt: string;
32
38
  /** Alias (phone/email) the request is addressed to, for identity matching. */
@@ -53,6 +59,9 @@ export interface RtpRequest {
53
59
  payee: RtpParty;
54
60
  narration?: string;
55
61
  transactionRef?: string;
62
+ orderNumber?: string;
63
+ deepLink?: string;
64
+ actionMessage?: string;
56
65
  expiresAt: string;
57
66
  recipient?: string;
58
67
  }
@@ -145,6 +154,8 @@ export interface RtpEventMap {
145
154
  export interface RtpClient {
146
155
  isRtp: (payload: unknown) => payload is RtpPayload;
147
156
  receive: (payload: unknown) => Promise<RtpReceiveOutcome>;
157
+ /** Fetch/refresh authorization details for a request from the backend. */
158
+ retrieve: (id: string) => Promise<RtpResult>;
148
159
  resumeAuth: () => void;
149
160
  accept: (params: {
150
161
  id: string;
package/src/validate.ts CHANGED
@@ -78,11 +78,59 @@ export function toRtpRequest(payload: RtpPayload): RtpRequest {
78
78
  payee: payload.payee,
79
79
  narration: payload.narration,
80
80
  transactionRef: payload.transactionRef,
81
+ orderNumber: payload.orderNumber,
82
+ deepLink: payload.deepLink,
83
+ actionMessage: payload.actionMessage,
81
84
  expiresAt: payload.expiresAt,
82
85
  recipient: payload.recipient,
83
86
  };
84
87
  }
85
88
 
89
+ /**
90
+ * Build a partial-request patch from an incoming payload, copying only the
91
+ * fields that are actually present. Used to overlay retrieved backend details
92
+ * without a partial response erasing data already held from the push.
93
+ */
94
+ export function toRequestPatch(
95
+ payload: Partial<RtpPayload>
96
+ ): Partial<RtpRequest> {
97
+ const patch: Partial<RtpRequest> = {};
98
+ if (payload.rtpId !== undefined) patch.id = payload.rtpId;
99
+ if (payload.amount !== undefined) patch.amount = toAmount(payload.amount);
100
+ if (payload.currency !== undefined) patch.currency = payload.currency;
101
+ if (payload.payer !== undefined) patch.payer = payload.payer;
102
+ if (payload.payee !== undefined) patch.payee = payload.payee;
103
+ if (payload.narration !== undefined) patch.narration = payload.narration;
104
+ if (payload.transactionRef !== undefined)
105
+ patch.transactionRef = payload.transactionRef;
106
+ if (payload.orderNumber !== undefined)
107
+ patch.orderNumber = payload.orderNumber;
108
+ if (payload.deepLink !== undefined) patch.deepLink = payload.deepLink;
109
+ if (payload.actionMessage !== undefined)
110
+ patch.actionMessage = payload.actionMessage;
111
+ if (payload.expiresAt !== undefined) patch.expiresAt = payload.expiresAt;
112
+ if (payload.recipient !== undefined) patch.recipient = payload.recipient;
113
+ return patch;
114
+ }
115
+
116
+ /**
117
+ * Overlay a patch onto an existing request. Only defined, non-null values are
118
+ * copied so a partial response never erases information already held.
119
+ */
120
+ export function mergeRequest(
121
+ current: RtpRequest,
122
+ patch: Partial<RtpRequest>
123
+ ): RtpRequest {
124
+ const next: RtpRequest = { ...current };
125
+ for (const key of Object.keys(patch) as (keyof RtpRequest)[]) {
126
+ const value = patch[key];
127
+ if (value !== undefined && value !== null) {
128
+ (next as any)[key] = value;
129
+ }
130
+ }
131
+ return next;
132
+ }
133
+
86
134
  /** Pure expiry check. Re-evaluated on every transition, not just on receive. */
87
135
  export function isExpired(request: RtpRequest, now: number): boolean {
88
136
  const expiry = new Date(request.expiresAt).getTime();