@inno-isw/react-native-rtp 0.0.0-stage → 0.1.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/LICENSE +20 -0
- package/README.md +362 -2
- package/lib/module/client.js +218 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/constants.js +44 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/emitter.js +46 -0
- package/lib/module/emitter.js.map +1 -0
- package/lib/module/index.js +16 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/mock/makeRtpPayload.js +51 -0
- package/lib/module/mock/makeRtpPayload.js.map +1 -0
- package/lib/module/mock/mockTransport.js +82 -0
- package/lib/module/mock/mockTransport.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/push.js +96 -0
- package/lib/module/push.js.map +1 -0
- package/lib/module/react/RtpProvider.js +142 -0
- package/lib/module/react/RtpProvider.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/validate.js +80 -0
- package/lib/module/validate.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/client.d.ts +11 -0
- package/lib/typescript/src/client.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +45 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/emitter.d.ts +15 -0
- package/lib/typescript/src/emitter.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +14 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/mock/makeRtpPayload.d.ts +17 -0
- package/lib/typescript/src/mock/makeRtpPayload.d.ts.map +1 -0
- package/lib/typescript/src/mock/mockTransport.d.ts +29 -0
- package/lib/typescript/src/mock/mockTransport.d.ts.map +1 -0
- package/lib/typescript/src/push.d.ts +41 -0
- package/lib/typescript/src/push.d.ts.map +1 -0
- package/lib/typescript/src/react/RtpProvider.d.ts +56 -0
- package/lib/typescript/src/react/RtpProvider.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +142 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/validate.d.ts +22 -0
- package/lib/typescript/src/validate.d.ts.map +1 -0
- package/package.json +141 -4
- package/src/client.ts +239 -0
- package/src/constants.ts +49 -0
- package/src/emitter.ts +53 -0
- package/src/index.tsx +43 -0
- package/src/mock/makeRtpPayload.ts +66 -0
- package/src/mock/mockTransport.ts +103 -0
- package/src/push.ts +109 -0
- package/src/react/RtpProvider.tsx +200 -0
- package/src/types.ts +161 -0
- package/src/validate.ts +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quickteller
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,363 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @inno-isw/react-native-rtp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Request to Pay (RTP) SDK for React Native.
|
|
4
|
+
|
|
5
|
+
`@inno-isw/react-native-rtp` owns the **business logic** of a Request to Pay flow — payload
|
|
6
|
+
validation, de-duplication, expiry, recipient matching, normalized status, and
|
|
7
|
+
the accept / decline / status calls. The **host app owns all UI**: it renders the
|
|
8
|
+
bottom sheet, collects the PIN, and decides navigation.
|
|
9
|
+
|
|
10
|
+
It is pure JS/TS with **zero native code** and **zero runtime dependencies**
|
|
11
|
+
(only `react` / `react-native` peer deps). Every external effect is injected, so
|
|
12
|
+
the same contract can be re-implemented on Kotlin or Swift.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
yarn add @inno-isw/react-native-rtp
|
|
20
|
+
# or: npm install @inno-isw/react-native-rtp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Peer dependencies: `react`, `react-native`.
|
|
24
|
+
|
|
25
|
+
### Publishing (maintainers)
|
|
26
|
+
|
|
27
|
+
The package is consumed as a normal npm dependency — do **not** link it locally.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
yarn install
|
|
31
|
+
yarn prepare # builds lib/ (bob build) via the prepare script
|
|
32
|
+
npm publish # publishes @inno-isw/react-native-rtp to the npm registry
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Consumer (e.g. QTM) then installs it like any other package:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
yarn add @inno-isw/react-native-rtp
|
|
39
|
+
# or: npm install @inno-isw/react-native-rtp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## The portable contract
|
|
45
|
+
|
|
46
|
+
The SDK never sees a push notification. It sees a **decoded RTP payload**. Each
|
|
47
|
+
platform is responsible for mapping *its* push shape (RTCP, raw FCM, APNs,
|
|
48
|
+
in-house PN) into this structure.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface RtpPayload {
|
|
52
|
+
version?: string; // schema version; unknown values are ignored
|
|
53
|
+
type: 'RTP'; // discriminator
|
|
54
|
+
rtpId: string; // stable id — the idempotency key
|
|
55
|
+
amount: string; // decimal string, e.g. "5000.00"
|
|
56
|
+
currency: string; // ISO 4217, e.g. "NGN"
|
|
57
|
+
merchant: { name: string; accountNumber?: string; bank?: string; logoUrl?: string };
|
|
58
|
+
transactionRef?: string;
|
|
59
|
+
expiresAt: string; // ISO 8601
|
|
60
|
+
recipient?: string; // alias the request is addressed to
|
|
61
|
+
[key: string]: unknown; // forward-compatible passthrough
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> Unknown fields are **ignored, not rejected**, so older SDKs keep working when
|
|
66
|
+
> the backend adds fields.
|
|
67
|
+
|
|
68
|
+
### `isRtp(payload)`
|
|
69
|
+
|
|
70
|
+
The type guard operates on the **decoded payload**, not the notification
|
|
71
|
+
envelope:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
payload.type === 'RTP' && typeof payload.rtpId === 'string';
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
### 1. Create the client (injected seams)
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createRtpClient } from '@inno-isw/react-native-rtp';
|
|
85
|
+
import { api } from './api'; // your existing HTTP client / ky / axios instance
|
|
86
|
+
|
|
87
|
+
export const rtpClient = createRtpClient({
|
|
88
|
+
// (required) minimal HTTP contract — the SDK never imports a network lib
|
|
89
|
+
transport: {
|
|
90
|
+
post: (path, body, headers) => api.post(path, { json: body, headers }),
|
|
91
|
+
get: (path, headers) => api.get(path, { headers }),
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// (optional) auth headers, called per request so tokens/MACs stay fresh
|
|
95
|
+
getAuthHeaders: async () => ({ Authorization: `Bearer ${await getToken()}` }),
|
|
96
|
+
|
|
97
|
+
// (optional) drives the auth gate + recipient matching
|
|
98
|
+
getCurrentUser: () => authStore.user?.email ?? null,
|
|
99
|
+
|
|
100
|
+
// (optional) injectable clock for deterministic expiry
|
|
101
|
+
now: () => Date.now(),
|
|
102
|
+
|
|
103
|
+
// (optional) diagnostics sink
|
|
104
|
+
logger: (...args) => console.log('[rtp]', ...args),
|
|
105
|
+
|
|
106
|
+
// (optional) REST base path, default "/rtp"
|
|
107
|
+
basePath: '/rtp',
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Normalize your push notification → payload
|
|
112
|
+
|
|
113
|
+
This is the **only** platform-specific input seam. Use the reference helper, or
|
|
114
|
+
write your own:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { mapPushToRtp } from '@inno-isw/react-native-rtp';
|
|
118
|
+
|
|
119
|
+
// Works with RTCP/FCM shapes, including app_data JSON unwrapping:
|
|
120
|
+
const payload = mapPushToRtp(notification);
|
|
121
|
+
if (payload) {
|
|
122
|
+
rtpClient.receive(payload);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For a bespoke PN system, pass your own data accessor:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const payload = mapPushToRtp(envelope, (e) => e.customKeys);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 3. Mount the provider and build your UI
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { RtpProvider, useRtp, RTP_STATUS } from '@inno-isw/react-native-rtp';
|
|
136
|
+
import { rtpClient } from './rtpClient';
|
|
137
|
+
|
|
138
|
+
export default function App() {
|
|
139
|
+
const isAuthenticated = useAuthStore((s) => Boolean(s.user));
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<RtpProvider
|
|
143
|
+
client={rtpClient}
|
|
144
|
+
isAuthenticated={isAuthenticated}
|
|
145
|
+
storage={persistence} // optional — see "Cold start" below
|
|
146
|
+
>
|
|
147
|
+
<Dashboard />
|
|
148
|
+
</RtpProvider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Inside your screen, render your own sheet (e.g. QTM's `QTBottomSheet`):
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
function Dashboard() {
|
|
157
|
+
const { request, status, isReady, accept, decline, dismiss, lastResult } =
|
|
158
|
+
useRtp();
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<YourDashboard />
|
|
163
|
+
|
|
164
|
+
<QTBottomSheet isOpen={status !== null} onClose={dismiss}>
|
|
165
|
+
{request && (
|
|
166
|
+
<>
|
|
167
|
+
<Text>{request.merchant.name}</Text>
|
|
168
|
+
<Text>{request.currency} {request.amount}</Text>
|
|
169
|
+
<Text>{request.transactionRef}</Text>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{isReady && (
|
|
174
|
+
<>
|
|
175
|
+
{/* collect PIN yourself, produce an opaque credential, then: */}
|
|
176
|
+
<Button title="Accept" onPress={() => accept(credential)} />
|
|
177
|
+
<Button title="Decline" onPress={() => decline('USER_DECLINED')} />
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{status === RTP_STATUS.ACCEPTED && <Text>Payment accepted</Text>}
|
|
182
|
+
{status === RTP_STATUS.FAILED && <Text>Something went wrong</Text>}
|
|
183
|
+
{lastResult?.error && <Text>{lastResult.error}</Text>}
|
|
184
|
+
</QTBottomSheet>
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Status catalog
|
|
193
|
+
|
|
194
|
+
The SDK computes status; the app only renders it.
|
|
195
|
+
|
|
196
|
+
| Status | Meaning |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `RECEIVED` | Payload arrived and passed validation. |
|
|
199
|
+
| `AWAITING_AUTH` | Valid, but the user is logged out (auth gate). |
|
|
200
|
+
| `READY` | Valid, authenticated, actionable — show Accept / Decline. |
|
|
201
|
+
| `PROCESSING` | Accept/decline in flight. |
|
|
202
|
+
| `ACCEPTED` | Backend confirmed acceptance. |
|
|
203
|
+
| `DECLINED` | Backend confirmed (or user initiated) a decline. |
|
|
204
|
+
| `EXPIRED` | Past `expiresAt`; no longer actionable. |
|
|
205
|
+
| `FAILED` | Terminal failure. See `RtpError` for the reason. |
|
|
206
|
+
|
|
207
|
+
`RtpError` values: `INVALID_PAYLOAD`, `RECIPIENT_MISMATCH`, `NETWORK_ERROR`,
|
|
208
|
+
`BACKEND_REJECTED`, `UNKNOWN`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Events
|
|
213
|
+
|
|
214
|
+
Subscribe via `client.on(event, handler)`. Every call returns an unsubscribe
|
|
215
|
+
function.
|
|
216
|
+
|
|
217
|
+
| Event | Payload | Fires when |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| `request` | `RtpRequest` | A request becomes actionable (`READY`). |
|
|
220
|
+
| `state` | `{ id, status }` | Any status transition. |
|
|
221
|
+
| `result` | `RtpResult` | A terminal outcome (`ACCEPTED`/`DECLINED`/`EXPIRED`/`FAILED`). |
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const off = rtpClient.on('result', (r) => {
|
|
225
|
+
if (r.status === 'ACCEPTED') showToast('Paid');
|
|
226
|
+
});
|
|
227
|
+
// later
|
|
228
|
+
off();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Client API
|
|
234
|
+
|
|
235
|
+
| Method | Description |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `isRtp(payload)` | Type guard for decoded RTP payloads. |
|
|
238
|
+
| `receive(payload)` | Validate, de-dupe, apply the auth gate/expiry, and emit. Returns an outcome. |
|
|
239
|
+
| `accept({ id, credential })` | Authorize a request. `credential` is **opaque** — the SDK forwards it untouched. |
|
|
240
|
+
| `decline({ id, reason })` | Decline a request. |
|
|
241
|
+
| `getStatus(id)` | Current normalized status. |
|
|
242
|
+
| `getRequest(id)` | The normalized request. |
|
|
243
|
+
| `acknowledge(id)` | Forget a request (after you dismiss the UI). |
|
|
244
|
+
| `resumeAuth()` | Release requests waiting on the auth gate. Usually called for you by the provider. |
|
|
245
|
+
| `on(event, cb)` | Subscribe; returns an unsubscribe fn. |
|
|
246
|
+
|
|
247
|
+
### The opaque credential
|
|
248
|
+
|
|
249
|
+
`accept()` takes a `credential` that the SDK **never inspects** — no PIN, no
|
|
250
|
+
crypto, no card-service output crosses the boundary as structured data:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const credential = await cardService.generateSecureData(pin); // stays in the app
|
|
254
|
+
await accept(credential);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
This is what makes the portability promise hold: a Kotlin or Swift host produces
|
|
258
|
+
its own credential type and posts it opaquely.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Edge cases handled for you
|
|
263
|
+
|
|
264
|
+
- **Duplicate delivery** — the same `rtpId` arriving via foreground *and*
|
|
265
|
+
background (or a double tap) emits `request` exactly once.
|
|
266
|
+
- **Expiry re-checked on every transition** — a request can expire while the
|
|
267
|
+
sheet is open; `accept` refuses and reports `EXPIRED`.
|
|
268
|
+
- **Recipient mismatch** — if `payload.recipient` doesn't match `getCurrentUser()`,
|
|
269
|
+
the request fails with `RECIPIENT_MISMATCH` instead of being shown.
|
|
270
|
+
- **Logged-out → login resume** — a request received while logged out sits in
|
|
271
|
+
`AWAITING_AUTH`; when `isAuthenticated` flips true the provider calls
|
|
272
|
+
`resumeAuth()` and emits `request`.
|
|
273
|
+
|
|
274
|
+
### Cold start (killed app)
|
|
275
|
+
|
|
276
|
+
Register your PN handler early (before React mounts) and persist the payload:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// index.js — before App renders
|
|
280
|
+
rtcp.onNotificationTapped(async (notification) => {
|
|
281
|
+
const payload = mapPushToRtp(notification);
|
|
282
|
+
if (payload) await persistence.setItem('rtp.pendingPayload', JSON.stringify(payload));
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`RtpProvider` drains that key on mount. The storage key is
|
|
287
|
+
`rtp.pendingPayload` (exported as `STORAGE_KEY` in the example).
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
export const persistence = {
|
|
291
|
+
getItem: (k) => AsyncStorage.getItem(k),
|
|
292
|
+
setItem: (k, v) => AsyncStorage.setItem(k, v),
|
|
293
|
+
removeItem: (k) => AsyncStorage.removeItem(k),
|
|
294
|
+
};
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Security notes
|
|
300
|
+
|
|
301
|
+
- **Never trust the push body for money movement.** It carries an opaque
|
|
302
|
+
`rtpId`/token; `accept` is verified server-side.
|
|
303
|
+
- **Recipient matching is enforced by the SDK** via `getCurrentUser()`, which
|
|
304
|
+
matters most in the logged-out flow where you have no session to lean on.
|
|
305
|
+
- **Idempotency is keyed by `rtpId`**, so replays can't double-action a request.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Cross-platform conformance
|
|
310
|
+
|
|
311
|
+
This package is one of several implementations of the same spec. To keep them
|
|
312
|
+
honest, `fixtures/conformance.json` holds platform-neutral test vectors with a
|
|
313
|
+
**fixed clock**: valid, logged-out, expired, malformed, wrong-recipient,
|
|
314
|
+
non-RTP, duplicate, and unknown-field cases.
|
|
315
|
+
|
|
316
|
+
Every implementation (React Native, Kotlin, Swift) must produce the same
|
|
317
|
+
outcome for each case. The vectors are the contract; the code is just one
|
|
318
|
+
realization of it.
|
|
319
|
+
|
|
320
|
+
> If you ever add signed payloads (HMAC), lock the canonical byte string
|
|
321
|
+
> (field order, encoding, key/digest encoding, MAC placement) in these vectors
|
|
322
|
+
> first — otherwise signatures silently diverge between platforms.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Example app
|
|
327
|
+
|
|
328
|
+
A runnable host app lives in [`example/`](./example). It ships a mock transport
|
|
329
|
+
and a control panel to simulate incoming PNs and every edge case:
|
|
330
|
+
|
|
331
|
+
```sh
|
|
332
|
+
yarn example start # then press i / a / w
|
|
333
|
+
yarn example web # or run it in a browser
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Buttons: deliver valid / expired / duplicate requests, simulate a cold start
|
|
337
|
+
(persist + remount provider), toggle authentication, and force accept failures.
|
|
338
|
+
The event log shows the `request` / `state` / `result` stream live.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```sh
|
|
345
|
+
yarn typecheck
|
|
346
|
+
yarn lint
|
|
347
|
+
yarn test
|
|
348
|
+
yarn prepare # build lib/
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Contributing
|
|
352
|
+
|
|
353
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
354
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
355
|
+
- [Code of conduct](./CODE_OF_CONDUCT.md)
|
|
356
|
+
|
|
357
|
+
## License
|
|
358
|
+
|
|
359
|
+
MIT
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { RTP_ERROR, RTP_EVENT, RTP_STATUS } from "./constants.js";
|
|
4
|
+
import { createEmitter, defaultNow, noopLogger } from "./emitter.js";
|
|
5
|
+
import { findValidationErrors, isExpired, isRtpPayload, toRtpRequest } from "./validate.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a portable Request-to-Pay client.
|
|
8
|
+
*
|
|
9
|
+
* The client owns validation, de-duplication, expiry, status computation and
|
|
10
|
+
* the accept/decline/status calls. It owns no UI, no navigation and no crypto.
|
|
11
|
+
* Every external effect is injected so the same contract can be re-implemented
|
|
12
|
+
* on other platforms.
|
|
13
|
+
*/
|
|
14
|
+
export function createRtpClient(options) {
|
|
15
|
+
const {
|
|
16
|
+
transport,
|
|
17
|
+
getAuthHeaders = async () => ({}),
|
|
18
|
+
getCurrentUser = () => null,
|
|
19
|
+
now = defaultNow,
|
|
20
|
+
logger = noopLogger,
|
|
21
|
+
basePath = '/rtp'
|
|
22
|
+
} = options;
|
|
23
|
+
if (!transport) {
|
|
24
|
+
throw new Error('[react-native-rtp] `transport` is required');
|
|
25
|
+
}
|
|
26
|
+
const emitter = createEmitter();
|
|
27
|
+
const entries = new Map();
|
|
28
|
+
function setStatus(id, status) {
|
|
29
|
+
const entry = entries.get(id);
|
|
30
|
+
if (entry) {
|
|
31
|
+
entry.status = status;
|
|
32
|
+
}
|
|
33
|
+
emitter.emit(RTP_EVENT.STATE, {
|
|
34
|
+
id,
|
|
35
|
+
status
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function emitResult(result) {
|
|
39
|
+
setStatus(result.id, result.status);
|
|
40
|
+
emitter.emit(RTP_EVENT.RESULT, result);
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function fail(id, error) {
|
|
44
|
+
return emitResult({
|
|
45
|
+
id,
|
|
46
|
+
status: RTP_STATUS.FAILED,
|
|
47
|
+
error
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function receive(payload) {
|
|
51
|
+
if (!isRtpPayload(payload)) {
|
|
52
|
+
return {
|
|
53
|
+
handled: false
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const id = payload.rtpId;
|
|
57
|
+
|
|
58
|
+
// Idempotency: the same request can arrive twice (foreground + background,
|
|
59
|
+
// or a double tap). Never re-emit for an id we have already seen.
|
|
60
|
+
const existing = entries.get(id);
|
|
61
|
+
if (existing) {
|
|
62
|
+
return {
|
|
63
|
+
handled: true,
|
|
64
|
+
request: existing.request,
|
|
65
|
+
status: existing.status
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const errors = findValidationErrors(payload);
|
|
69
|
+
if (errors.length > 0) {
|
|
70
|
+
logger('[react-native-rtp] invalid payload, missing:', errors.join(', '));
|
|
71
|
+
return {
|
|
72
|
+
handled: true,
|
|
73
|
+
error: RTP_ERROR.INVALID_PAYLOAD
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const request = toRtpRequest(payload);
|
|
77
|
+
entries.set(id, {
|
|
78
|
+
request,
|
|
79
|
+
status: RTP_STATUS.RECEIVED
|
|
80
|
+
});
|
|
81
|
+
if (isExpired(request, now())) {
|
|
82
|
+
const result = emitResult({
|
|
83
|
+
id,
|
|
84
|
+
status: RTP_STATUS.EXPIRED
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
handled: true,
|
|
88
|
+
request,
|
|
89
|
+
status: result.status
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const currentUser = getCurrentUser();
|
|
93
|
+
if (!currentUser) {
|
|
94
|
+
setStatus(id, RTP_STATUS.AWAITING_AUTH);
|
|
95
|
+
return {
|
|
96
|
+
handled: true,
|
|
97
|
+
request,
|
|
98
|
+
status: RTP_STATUS.AWAITING_AUTH
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (request.recipient && currentUser !== request.recipient) {
|
|
102
|
+
const result = fail(id, RTP_ERROR.RECIPIENT_MISMATCH);
|
|
103
|
+
return {
|
|
104
|
+
handled: true,
|
|
105
|
+
request,
|
|
106
|
+
status: result.status,
|
|
107
|
+
error: result.error
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
setStatus(id, RTP_STATUS.READY);
|
|
111
|
+
emitter.emit(RTP_EVENT.REQUEST, request);
|
|
112
|
+
return {
|
|
113
|
+
handled: true,
|
|
114
|
+
request,
|
|
115
|
+
status: RTP_STATUS.READY
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Called by the host after a successful login to release any requests that
|
|
121
|
+
* were waiting on the auth gate.
|
|
122
|
+
*/
|
|
123
|
+
function resumeAuth() {
|
|
124
|
+
for (const [id, entry] of entries) {
|
|
125
|
+
if (entry.status !== RTP_STATUS.AWAITING_AUTH) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (isExpired(entry.request, now())) {
|
|
129
|
+
const result = emitResult({
|
|
130
|
+
id,
|
|
131
|
+
status: RTP_STATUS.EXPIRED
|
|
132
|
+
});
|
|
133
|
+
logger('[react-native-rtp] request expired while awaiting auth:', id, result.status);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const currentUser = getCurrentUser();
|
|
137
|
+
if (entry.request.recipient && currentUser && currentUser !== entry.request.recipient) {
|
|
138
|
+
fail(id, RTP_ERROR.RECIPIENT_MISMATCH);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
setStatus(id, RTP_STATUS.READY);
|
|
142
|
+
emitter.emit(RTP_EVENT.REQUEST, entry.request);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function accept(params) {
|
|
146
|
+
const {
|
|
147
|
+
id,
|
|
148
|
+
credential
|
|
149
|
+
} = params;
|
|
150
|
+
const entry = entries.get(id);
|
|
151
|
+
if (!entry) {
|
|
152
|
+
return fail(id, RTP_ERROR.UNKNOWN);
|
|
153
|
+
}
|
|
154
|
+
if (isExpired(entry.request, now())) {
|
|
155
|
+
return emitResult({
|
|
156
|
+
id,
|
|
157
|
+
status: RTP_STATUS.EXPIRED
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
setStatus(id, RTP_STATUS.PROCESSING);
|
|
161
|
+
try {
|
|
162
|
+
const headers = await getAuthHeaders();
|
|
163
|
+
const res = await transport.post(`${basePath}/${id}/accept`, {
|
|
164
|
+
credential
|
|
165
|
+
}, headers);
|
|
166
|
+
const backendStatus = res?.data?.status;
|
|
167
|
+
const status = backendStatus === 'DECLINED' ? RTP_STATUS.DECLINED : backendStatus === 'FAILED' ? RTP_STATUS.FAILED : RTP_STATUS.ACCEPTED;
|
|
168
|
+
return emitResult({
|
|
169
|
+
id,
|
|
170
|
+
status,
|
|
171
|
+
data: res?.data,
|
|
172
|
+
error: status === RTP_STATUS.FAILED ? RTP_ERROR.BACKEND_REJECTED : undefined
|
|
173
|
+
});
|
|
174
|
+
} catch (err) {
|
|
175
|
+
logger('[react-native-rtp] accept failed:', err);
|
|
176
|
+
return fail(id, RTP_ERROR.NETWORK_ERROR);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function decline(params) {
|
|
180
|
+
const {
|
|
181
|
+
id,
|
|
182
|
+
reason
|
|
183
|
+
} = params;
|
|
184
|
+
const entry = entries.get(id);
|
|
185
|
+
if (!entry) {
|
|
186
|
+
return fail(id, RTP_ERROR.UNKNOWN);
|
|
187
|
+
}
|
|
188
|
+
setStatus(id, RTP_STATUS.PROCESSING);
|
|
189
|
+
try {
|
|
190
|
+
const headers = await getAuthHeaders();
|
|
191
|
+
const res = await transport.post(`${basePath}/${id}/decline`, {
|
|
192
|
+
reason
|
|
193
|
+
}, headers);
|
|
194
|
+
return emitResult({
|
|
195
|
+
id,
|
|
196
|
+
status: RTP_STATUS.DECLINED,
|
|
197
|
+
data: res?.data
|
|
198
|
+
});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
logger('[react-native-rtp] decline failed:', err);
|
|
201
|
+
return fail(id, RTP_ERROR.NETWORK_ERROR);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
isRtp: isRtpPayload,
|
|
206
|
+
receive,
|
|
207
|
+
resumeAuth,
|
|
208
|
+
accept,
|
|
209
|
+
decline,
|
|
210
|
+
getStatus: id => entries.get(id)?.status,
|
|
211
|
+
getRequest: id => entries.get(id)?.request,
|
|
212
|
+
acknowledge: id => {
|
|
213
|
+
entries.delete(id);
|
|
214
|
+
},
|
|
215
|
+
on: emitter.on
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["RTP_ERROR","RTP_EVENT","RTP_STATUS","createEmitter","defaultNow","noopLogger","findValidationErrors","isExpired","isRtpPayload","toRtpRequest","createRtpClient","options","transport","getAuthHeaders","getCurrentUser","now","logger","basePath","Error","emitter","entries","Map","setStatus","id","status","entry","get","emit","STATE","emitResult","result","RESULT","fail","error","FAILED","receive","payload","handled","rtpId","existing","request","errors","length","join","INVALID_PAYLOAD","set","RECEIVED","EXPIRED","currentUser","AWAITING_AUTH","recipient","RECIPIENT_MISMATCH","READY","REQUEST","resumeAuth","accept","params","credential","UNKNOWN","PROCESSING","headers","res","post","backendStatus","data","DECLINED","ACCEPTED","BACKEND_REJECTED","undefined","err","NETWORK_ERROR","decline","reason","isRtp","getStatus","getRequest","acknowledge","delete","on"],"sourceRoot":"../../src","sources":["client.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalized RTP status.
|
|
5
|
+
*
|
|
6
|
+
* The SDK is the single source of truth for these values so that every host
|
|
7
|
+
* platform (React Native, Kotlin, Swift) renders the same UI states.
|
|
8
|
+
*/
|
|
9
|
+
export const RTP_STATUS = {
|
|
10
|
+
/** Payload arrived and passed validation, but is not yet actionable. */
|
|
11
|
+
RECEIVED: 'RECEIVED',
|
|
12
|
+
/** Expired before it could be actioned. */
|
|
13
|
+
EXPIRED: 'EXPIRED',
|
|
14
|
+
/** Valid, but the user is not authenticated yet (login gate). */
|
|
15
|
+
AWAITING_AUTH: 'AWAITING_AUTH',
|
|
16
|
+
/** Valid, authenticated, and ready for the user to accept/decline. */
|
|
17
|
+
READY: 'READY',
|
|
18
|
+
/** Accept/decline in flight. */
|
|
19
|
+
PROCESSING: 'PROCESSING',
|
|
20
|
+
/** Backend confirmed acceptance. */
|
|
21
|
+
ACCEPTED: 'ACCEPTED',
|
|
22
|
+
/** Backend confirmed (or user initiated) a decline. */
|
|
23
|
+
DECLINED: 'DECLINED',
|
|
24
|
+
/** Terminal failure (network, validation, recipient mismatch, etc.). */
|
|
25
|
+
FAILED: 'FAILED'
|
|
26
|
+
};
|
|
27
|
+
/** Events emitted by the client. */
|
|
28
|
+
export const RTP_EVENT = {
|
|
29
|
+
/** A new, actionable request is available. */
|
|
30
|
+
REQUEST: 'request',
|
|
31
|
+
/** Status transition for a request. */
|
|
32
|
+
STATE: 'state',
|
|
33
|
+
/** Terminal outcome for a request. */
|
|
34
|
+
RESULT: 'result'
|
|
35
|
+
};
|
|
36
|
+
/** Reasons a request can end in FAILED, exposed for UI/diagnostics. */
|
|
37
|
+
export const RTP_ERROR = {
|
|
38
|
+
INVALID_PAYLOAD: 'INVALID_PAYLOAD',
|
|
39
|
+
RECIPIENT_MISMATCH: 'RECIPIENT_MISMATCH',
|
|
40
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
41
|
+
BACKEND_REJECTED: 'BACKEND_REJECTED',
|
|
42
|
+
UNKNOWN: 'UNKNOWN'
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["RTP_STATUS","RECEIVED","EXPIRED","AWAITING_AUTH","READY","PROCESSING","ACCEPTED","DECLINED","FAILED","RTP_EVENT","REQUEST","STATE","RESULT","RTP_ERROR","INVALID_PAYLOAD","RECIPIENT_MISMATCH","NETWORK_ERROR","BACKEND_REJECTED","UNKNOWN"],"sourceRoot":"../../src","sources":["constants.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,UAAU,GAAG;EACxB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,OAAO,EAAE,SAAS;EAClB;EACAC,aAAa,EAAE,eAAe;EAC9B;EACAC,KAAK,EAAE,OAAO;EACd;EACAC,UAAU,EAAE,YAAY;EACxB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,MAAM,EAAE;AACV,CAAU;AAIV;AACA,OAAO,MAAMC,SAAS,GAAG;EACvB;EACAC,OAAO,EAAE,SAAS;EAClB;EACAC,KAAK,EAAE,OAAO;EACd;EACAC,MAAM,EAAE;AACV,CAAU;AAIV;AACA,OAAO,MAAMC,SAAS,GAAG;EACvBC,eAAe,EAAE,iBAAiB;EAClCC,kBAAkB,EAAE,oBAAoB;EACxCC,aAAa,EAAE,eAAe;EAC9BC,gBAAgB,EAAE,kBAAkB;EACpCC,OAAO,EAAE;AACX,CAAU","ignoreList":[]}
|