@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.
- package/README.md +33 -10
- package/lib/module/client.js +184 -11
- package/lib/module/client.js.map +1 -1
- package/lib/module/constants.js +5 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/mock/makeRtpPayload.js +3 -0
- package/lib/module/mock/makeRtpPayload.js.map +1 -1
- package/lib/module/mock/mockTransport.js +9 -2
- package/lib/module/mock/mockTransport.js.map +1 -1
- package/lib/module/react/RtpProvider.js +13 -3
- package/lib/module/react/RtpProvider.js.map +1 -1
- package/lib/module/validate.js +42 -0
- package/lib/module/validate.js.map +1 -1
- package/lib/typescript/src/client.d.ts +12 -3
- package/lib/typescript/src/client.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +5 -0
- package/lib/typescript/src/constants.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mock/makeRtpPayload.d.ts.map +1 -1
- package/lib/typescript/src/mock/mockTransport.d.ts +7 -1
- package/lib/typescript/src/mock/mockTransport.d.ts.map +1 -1
- package/lib/typescript/src/react/RtpProvider.d.ts +6 -2
- package/lib/typescript/src/react/RtpProvider.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +11 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/validate.d.ts +11 -0
- package/lib/typescript/src/validate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +202 -13
- package/src/constants.ts +5 -0
- package/src/index.tsx +3 -1
- package/src/mock/makeRtpPayload.ts +3 -0
- package/src/mock/mockTransport.ts +18 -2
- package/src/react/RtpProvider.tsx +40 -5
- package/src/types.ts +11 -0
- 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: {
|
|
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 {
|
|
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) =>
|
|
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
|
-
[
|
|
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();
|