@ngandu-dev/flexpay 2.0.4 → 3.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.
- package/README.md +175 -125
- package/UPGRADE_3.0.0.md +345 -0
- package/dist/index.d.mts +186 -118
- package/dist/index.d.ts +186 -118
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +11 -6
package/UPGRADE_3.0.0.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Upgrade to FlexPay TypeScript 3.0.0
|
|
2
|
+
|
|
3
|
+
Version 3 moves mobile-money and card collections to FlexPay's corporate API and changes the public
|
|
4
|
+
client, status, and response contracts. This guide covers migration from version 2. Current usage
|
|
5
|
+
examples are in the [README](README.md).
|
|
6
|
+
|
|
7
|
+
## Configure explicit destinations
|
|
8
|
+
|
|
9
|
+
Replace positional merchant/token arguments with a `Credential` object and a required `BaseUrl`.
|
|
10
|
+
Remove imports of `Environment`, `EnvironmentType`, and `BaseUrls`. There is no default environment
|
|
11
|
+
or destination. Node.js 24 or newer remains required.
|
|
12
|
+
|
|
13
|
+
Before:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Client } from "@ngandu-dev/flexpay";
|
|
17
|
+
|
|
18
|
+
const client = new Client("MERCHANT_CODE", "YOUR_TOKEN", "prod");
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Client, type Credential, productionBaseUrl } from "@ngandu-dev/flexpay";
|
|
25
|
+
|
|
26
|
+
const credential = {
|
|
27
|
+
merchant: "MERCHANT_CODE",
|
|
28
|
+
token: "YOUR_TOKEN",
|
|
29
|
+
} satisfies Credential;
|
|
30
|
+
|
|
31
|
+
const client = new Client(credential, productionBaseUrl);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Replace `apiBaseUrl` and `cardBaseUrl` overrides with all three `BaseUrl` properties, even if your
|
|
35
|
+
application uses only one operation. Prefixes include the API version and omit the service path.
|
|
36
|
+
|
|
37
|
+
Before:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const localClient = new Client("TESTMERCHANT", "test-token", "dev", {
|
|
41
|
+
apiBaseUrl: "http://localhost:8080/api/rest/v1",
|
|
42
|
+
cardBaseUrl: "http://localhost:8080/v1.1",
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
After:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Client, type BaseUrl } from "@ngandu-dev/flexpay";
|
|
50
|
+
|
|
51
|
+
const baseUrl = {
|
|
52
|
+
payment: "http://localhost:8080/api/rest/v1",
|
|
53
|
+
check: "http://localhost:8080/api/rest/v1",
|
|
54
|
+
payout: "http://localhost:8080/api/rest/v1",
|
|
55
|
+
} satisfies BaseUrl;
|
|
56
|
+
|
|
57
|
+
const localClient = new Client(
|
|
58
|
+
{ merchant: "TESTMERCHANT", token: "test-token" },
|
|
59
|
+
baseUrl,
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`productionBaseUrl` contains these prefixes:
|
|
64
|
+
|
|
65
|
+
| Property | Prefix |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `payment` | `https://corporateapi.flexpay.cd/api/rest/v1` |
|
|
68
|
+
| `check` | `https://apicheck.flexpaie.com/api/rest/v1` |
|
|
69
|
+
| `payout` | `https://backend.flexpay.cd/api/rest/v1` |
|
|
70
|
+
|
|
71
|
+
FlexPay has not supplied a corporate sandbox URL. Configure your simulator explicitly instead of
|
|
72
|
+
relying on the removed `dev` environment. Both mobile and card now use `/paymentService`. Checks
|
|
73
|
+
use the separate check prefix. Payouts retain `/merchantPayOutService` and the existing wire protocol.
|
|
74
|
+
|
|
75
|
+
Configuration schemas reject unknown fields and blank credentials. Base URLs must be absolute
|
|
76
|
+
HTTP(S) prefixes without credentials, query strings, fragments, backslashes, or internal whitespace
|
|
77
|
+
and control characters. Surrounding whitespace and trailing slashes are removed from prefixes.
|
|
78
|
+
Merchant/token contents are preserved. The client retains immutable validated snapshots.
|
|
79
|
+
|
|
80
|
+
An optional third constructor argument accepts a Fetch-compatible function. It must honor the
|
|
81
|
+
provided abort signal and `redirect: "manual"` behavior.
|
|
82
|
+
|
|
83
|
+
## Replace payment dispatch and removed request fields
|
|
84
|
+
|
|
85
|
+
Replace `pay()` with `mobile()` or `card()`. Request schemas reject unknown fields rather than
|
|
86
|
+
silently stripping them. Remove obsolete fields from runtime objects as well as TypeScript types.
|
|
87
|
+
|
|
88
|
+
For mobile requests, remove `description`, `approveUrl`, `cancelUrl`, and `declineUrl`.
|
|
89
|
+
|
|
90
|
+
Before:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const mobile = await client.pay({
|
|
94
|
+
amount: 10,
|
|
95
|
+
currency: "USD",
|
|
96
|
+
phone: "243810000001",
|
|
97
|
+
reference: "mobile-order-123",
|
|
98
|
+
callbackUrl: "https://example.com/payments/callback",
|
|
99
|
+
description: "Order payment",
|
|
100
|
+
approveUrl: "https://example.com/payments/approved",
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const mobile = await client.mobile({
|
|
108
|
+
amount: 10,
|
|
109
|
+
currency: "USD",
|
|
110
|
+
phone: "243810000001",
|
|
111
|
+
reference: "mobile-order-123",
|
|
112
|
+
callbackUrl: "https://example.com/payments/callback",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!mobile.accepted || !mobile.orderNumber) {
|
|
116
|
+
throw new Error(mobile.message || "Payment initiation was not accepted");
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For card requests, remove `description` and `homeUrl`. The 25-character reference cap is removed.
|
|
121
|
+
|
|
122
|
+
Before:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const card = await client.card({
|
|
126
|
+
amount: 10,
|
|
127
|
+
currency: "USD",
|
|
128
|
+
reference: "card-order-123",
|
|
129
|
+
description: "Order payment",
|
|
130
|
+
homeUrl: "https://example.com",
|
|
131
|
+
callbackUrl: "https://example.com/payments/callback",
|
|
132
|
+
approveUrl: "https://example.com/payments/approved",
|
|
133
|
+
cancelUrl: "https://example.com/payments/cancelled",
|
|
134
|
+
declineUrl: "https://example.com/payments/declined",
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
After:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const card = await client.card({
|
|
142
|
+
amount: 10,
|
|
143
|
+
currency: "USD",
|
|
144
|
+
reference: "card-order-123",
|
|
145
|
+
callbackUrl: "https://example.com/payments/callback",
|
|
146
|
+
approveUrl: "https://example.com/payments/approved",
|
|
147
|
+
cancelUrl: "https://example.com/payments/cancelled",
|
|
148
|
+
declineUrl: "https://example.com/payments/declined",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!card.accepted || !card.orderNumber || !card.url) {
|
|
152
|
+
throw new Error(card.message || "Card checkout is unavailable");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Store card.orderNumber, then redirect the customer to card.url.
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Public fields remain camelCase. The SDK writes string types `"1"`/`"2"`, `callback_url`, and the
|
|
159
|
+
card redirect fields in snake_case. Authorization is sent only in the bearer header. Remove any
|
|
160
|
+
application-side dependency on body authorization, `home_url`, description, or the old card `/pay`
|
|
161
|
+
endpoint. Payout input fields remain `amount`, `currency`, `phone`, `reference`, and `callbackUrl`;
|
|
162
|
+
its wire body still uses numeric type `1` and `callbackUrl`.
|
|
163
|
+
|
|
164
|
+
Phones now require exactly 12 digits, rather than any 12 characters. Amounts must be positive finite
|
|
165
|
+
numbers. References, callback strings, and redirect strings must contain non-whitespace content;
|
|
166
|
+
the SDK does not validate callback/redirect strings as absolute URLs. Check identifiers cannot be
|
|
167
|
+
blank or exactly `.` or `..`. Request validation does not silently trim or rewrite your values.
|
|
168
|
+
|
|
169
|
+
## Separate acceptance from completion
|
|
170
|
+
|
|
171
|
+
Remove `client.isSuccessful()`. Use the result property for the operation you performed:
|
|
172
|
+
|
|
173
|
+
| Operation | Property | Meaning |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `mobile()`, `card()`, `payout()` | `accepted` | The provider returned initiation code 0. |
|
|
176
|
+
| `check()` | `successful` | Both lookup code and nested transaction status are 0. |
|
|
177
|
+
| `parseCallback()` | `successful` | The supplied callback claims code 0. |
|
|
178
|
+
|
|
179
|
+
An accepted initiation can still have a missing or empty order number or checkout URL. Check these
|
|
180
|
+
fields before using them. Acceptance does not confirm collection of funds.
|
|
181
|
+
|
|
182
|
+
Before:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const check = await client.check("ORDER_NUMBER");
|
|
186
|
+
|
|
187
|
+
if (client.isSuccessful(check)) {
|
|
188
|
+
// Version 2 checked only the top-level lookup code.
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
After:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const check = await client.check("ORDER_NUMBER");
|
|
196
|
+
|
|
197
|
+
if (check.successful) {
|
|
198
|
+
// The lookup succeeded and the nested transaction has a success status.
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
An observed live card check returned lookup code `0`, transaction status `4`, and a message saying
|
|
203
|
+
the operator declined that order. Version 3 preserves status `4` and returns `successful: false`.
|
|
204
|
+
This observation does not establish a global mapping for status `4`. A paid transaction with
|
|
205
|
+
nested status `0` and the provider's pending-status code remain unverified.
|
|
206
|
+
|
|
207
|
+
## Migrate status comparisons and serialization
|
|
208
|
+
|
|
209
|
+
`Status` is now an immutable value object. Every response `code` and transaction `status` is a
|
|
210
|
+
`Status` instance. Remove `Status.SUCCESS`, `Status.FAILURE`, and direct numeric comparisons.
|
|
211
|
+
|
|
212
|
+
Before:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { Status } from "@ngandu-dev/flexpay";
|
|
216
|
+
|
|
217
|
+
const hasSuccessCode = check.code === Status.SUCCESS;
|
|
218
|
+
const hasFailureCode = check.transaction?.status === Status.FAILURE;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
After:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const lookupSucceeded = check.code.isSuccessful();
|
|
225
|
+
const hasKnownFailureCode = check.transaction?.status.isFailure() === true;
|
|
226
|
+
const rawTransactionStatus = check.transaction?.status.value;
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`isSuccessful()` recognizes only `0`; `isFailure()` recognizes only `1`. Other safe integer codes
|
|
230
|
+
remain intact without an inferred state. For example, status `4` makes both methods return `false`.
|
|
231
|
+
Never treat `!status.isFailure()` as proof of payment success. Use `check.successful` for the full
|
|
232
|
+
check outcome, not `check.code.isSuccessful()` alone.
|
|
233
|
+
|
|
234
|
+
Use `new Status(integer)` for application-owned values. `StatusSchema` parses provider integers or
|
|
235
|
+
canonical integer strings into `Status`; it does not accept an already-normalized `Status` object.
|
|
236
|
+
|
|
237
|
+
JSON output changes with the value object:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { Status } from "@ngandu-dev/flexpay";
|
|
241
|
+
|
|
242
|
+
JSON.stringify(new Status(0)); // {"value":0}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Serializing a whole SDK response preserves these nested objects. `JSON.parse` does not restore
|
|
246
|
+
their methods. If your persistence or API contract requires numeric codes, map `.value` explicitly:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const storedCheck = {
|
|
250
|
+
code: check.code.value,
|
|
251
|
+
message: check.message,
|
|
252
|
+
transaction:
|
|
253
|
+
check.transaction === null
|
|
254
|
+
? null
|
|
255
|
+
: { ...check.transaction, status: check.transaction.status.value },
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const storedJson = JSON.stringify(storedCheck);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
To read that numeric representation back into SDK objects, parse it with `CheckResponseSchema`:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { CheckResponseSchema } from "@ngandu-dev/flexpay";
|
|
265
|
+
|
|
266
|
+
const restoredCheck = CheckResponseSchema.parse(JSON.parse(storedJson));
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Update response types and callback handling
|
|
270
|
+
|
|
271
|
+
`MobileResponse` now contains only `code`, `message`, `orderNumber`, and `accepted`.
|
|
272
|
+
`PayoutResponse` has the same shape. `CardResponse` adds nullable `url`. Reporting fields belong
|
|
273
|
+
to the new `CallbackResponse`, which has `successful`, amounts, phone, currency, timestamps,
|
|
274
|
+
channel, reference, order number, and `providerReference`.
|
|
275
|
+
|
|
276
|
+
Replace `client.handleCallback()` with standalone `parseCallback()`. No client or credential is
|
|
277
|
+
needed to parse a callback.
|
|
278
|
+
|
|
279
|
+
Before:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const callback = client.handleCallback(incomingBody);
|
|
283
|
+
const claimedSuccess = client.isSuccessful(callback);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
After:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { parseCallback } from "@ngandu-dev/flexpay";
|
|
290
|
+
|
|
291
|
+
const callback = parseCallback(incomingBody);
|
|
292
|
+
const claimedSuccess = callback.successful;
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The parser validates payload structure and reports the payload's claim. It does not authenticate
|
|
296
|
+
the sender or authorize fulfillment. Keep that verification in your application.
|
|
297
|
+
|
|
298
|
+
Only the provider's wire field `provider_reference` populates `callback.providerReference`.
|
|
299
|
+
An input containing only the old camelCase alias `providerReference` leaves the output field null;
|
|
300
|
+
provider response objects permit extra fields, so the alias does not raise a validation error.
|
|
301
|
+
|
|
302
|
+
Normalized results, nested transactions, and statuses are frozen and readonly. Optional wire
|
|
303
|
+
fields such as card URL and nested transaction order number now normalize to required nullable
|
|
304
|
+
output properties. Missing messages normalize to an empty string. An empty order number remains
|
|
305
|
+
an empty string. Do not mutate a result or rely on `undefined` for these fields.
|
|
306
|
+
|
|
307
|
+
Update hand-built typed mocks to include `Status` instances, derived outcome properties, and all
|
|
308
|
+
normalized nullable fields. Exported schemas can build consistent fixtures from wire data:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { MobileResponseSchema } from "@ngandu-dev/flexpay";
|
|
312
|
+
|
|
313
|
+
const mobileResponse = MobileResponseSchema.parse({
|
|
314
|
+
code: "0",
|
|
315
|
+
message: "Accepted",
|
|
316
|
+
orderNumber: "TEST-ORDER",
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Do not pass a serialized SDK response containing `{ "value": 0 }` into a provider-response schema.
|
|
321
|
+
Use raw numeric/string wire codes, or explicitly restore the numeric representation as shown above.
|
|
322
|
+
|
|
323
|
+
## Update error handling and retry assumptions
|
|
324
|
+
|
|
325
|
+
HTTP response parsing now occurs inside the transport. Malformed JSON or an invalid response
|
|
326
|
+
structure raises `NetworkException` with `type: "invalid_response"`, rather than a Zod error.
|
|
327
|
+
HTTP status and Content-Type remain available in the diagnostic. Parsed HTTP failures use
|
|
328
|
+
`type: "http_error"`; provider error bodies, error messages, and raw fetch/parser causes are no
|
|
329
|
+
longer copied into the exception. HTTP 200 business rejection codes remain decoded result objects.
|
|
330
|
+
|
|
331
|
+
The exception classes retain their HTTP mapping: 401/429 use `AccountException`, other 4xx use
|
|
332
|
+
`ClientException`, and 5xx use `ServerException`. Direct request/configuration validation and
|
|
333
|
+
`parseCallback` still raise Zod errors. Invalid check identifiers raise `Error`; invalid direct
|
|
334
|
+
`Status` construction raises `RangeError`.
|
|
335
|
+
|
|
336
|
+
Payment and payout POSTs are sent once. Only GET checks retry transient network, timeout, or 5xx
|
|
337
|
+
failures, up to three retries after the original attempt. Each attempt has a 30-second deadline.
|
|
338
|
+
Default waits are 500, 1,000, and 2,000 milliseconds. A numeric `Retry-After` of at most two seconds
|
|
339
|
+
can increase a wait; a longer, date-formatted, or invalid value suppresses retry. Known 4xx
|
|
340
|
+
responses do not retry, even if reading the body later fails.
|
|
341
|
+
|
|
342
|
+
Redirects now raise `NetworkException` with `type: "unexpected_redirect"` and are not followed.
|
|
343
|
+
Timeouts use `type: "timeout"`; unknown fetch failures use `type: "network_error"`. Unfinished
|
|
344
|
+
responses are closed when an attempt ends. A transport failure does not establish whether a
|
|
345
|
+
payment was accepted. Reconcile the original transaction before attempting another collection.
|