cybersource-brunei-payauth 0.2.2 → 0.2.4

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 CHANGED
@@ -232,7 +232,7 @@ const outcome = await client.voidTransaction(paymentId, { reason: 'customer canc
232
232
  ```
233
233
 
234
234
  - **`operation: 'reversal'`** — the authorization hadn't been captured yet, so this released the hold via an authorization reversal (`POST /pts/v2/payments/{id}/reversals`). The reversed amount is read automatically from Cybersource's own record of the authorized amount, not re-derived or guessed.
235
- - **`operation: 'capture-void'`** — a capture already existed, so this voided *that* instead (`POST /pts/v2/captures/{id}/voids`). Cybersource typically batches captures to the processor once a day, so this only works same-day; after that, use a refund instead (not currently exposed by this package).
235
+ - **`operation: 'capture-void'`** — a capture already existed, so this voided *that* instead (`POST /pts/v2/captures/{id}/voids`). Cybersource typically batches captures to the processor once a day, so this only works same-day; after that, use one of the refund methods below.
236
236
 
237
237
  `voidTransaction` assumes the only follow-on transaction it will ever see against one of its own authorizations is this package's own `capture()` call — if Cybersource reports *any* related transaction, it voids that one. If your integration creates other follow-on transactions against the same authorization outside this package (e.g. from the Cybersource Business Center), call the lower-level functions yourself instead:
238
238
 
@@ -244,7 +244,29 @@ await client.voidPayment(paymentId, { totalAmount, currency, reason, clientRefer
244
244
  await client.voidCapture(captureId, { clientReferenceCode }); // post-capture
245
245
  ```
246
246
 
247
- `voidPayment`'s `totalAmount`/`currency` must match the *authorized* amount from `authorize()`'s own response — not the amount you originally requested, which Cybersource notes can differ (e.g. partial approvals). It throws immediately, before any network call, if either is omitted, rather than sending Cybersource a request that would be declined anyway.
247
+ `voidPayment`'s `totalAmount`/`currency` must match the *authorized* amount from `authorize()`'s own response — not the amount you originally requested, which Cybersource notes can differ (e.g. partial approvals). It throws immediately, before any network call, if either is omitted, rather than sending Cybersource a request that would be declined anyway.
248
+
249
+ ## Refunding a settled payment
250
+
251
+ CyberSource provides two refund endpoints based on how the original payment was captured. Both methods support full or partial refunds; pass the amount to return, using the original transaction's currency:
252
+
253
+ ```js
254
+ // Use only when authorization and capture were combined in the original POST /pts/v2/payments.
255
+ await client.refundPayment(paymentId, {
256
+ totalAmount: '50.00',
257
+ currency: 'BND',
258
+ clientReferenceCode: 'refund-order-1', // optional
259
+ });
260
+
261
+ // Use when capture() was called separately. Pass capture()'s response id, not paymentId.
262
+ await client.refundCapture(captureId, {
263
+ totalAmount: '20.00',
264
+ currency: 'BND',
265
+ clientReferenceCode: 'partial-refund-order-1', // optional
266
+ });
267
+ ```
268
+
269
+ `refundPayment` calls `POST /pts/v2/payments/{paymentId}/refunds`; `refundCapture` calls `POST /pts/v2/captures/{captureId}/refunds`. Both require `totalAmount` and `currency` and return CyberSource's response unchanged, including the refund transaction `id` and `status`.
248
270
 
249
271
  ### Plain-language status descriptions
250
272
 
@@ -263,7 +285,7 @@ describeStatus('PENDING_AUTHENTICATION');
263
285
  // bank) before the charge can proceed."
264
286
  ```
265
287
 
266
- It covers every documented status value across `authorize()`, `capture()`, `voidPayment()`, and `voidCapture()`. `checkTransaction()`'s result already includes this as `statusDescription`, computed from its own `status` field. An unrecognized status (or `null`/missing) returns a safe fallback sentence rather than throwing — this is a plain-language convenience, not a validator.
288
+ It covers every documented status value across `authorize()`, `capture()`, `voidPayment()`, `voidCapture()`, `refundPayment()`, and `refundCapture()`. `checkTransaction()`'s result already includes this as `statusDescription`, computed from its own `status` field. An unrecognized status (or `null`/missing) returns a safe fallback sentence rather than throwing — this is a plain-language convenience, not a validator.
267
289
 
268
290
  ## BIN Lookup
269
291
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cybersource-brunei-payauth",
3
- "version": "0.2.2",
4
- "description": "Cybersource payer-authentication (3-D Secure) + payments client for Brunei merchants. Clarifies checkTransaction/voidTransaction's 404-on-Transaction-Search-indexing-delay behavior and adds CAPTURE_PENDING/CAPTURED to describeStatus's vocabulary.",
3
+ "version": "0.2.4",
4
+ "description": "Cybersource payer-authentication (3-D Secure) and payments client for Brunei merchants, including payment and capture refunds.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
package/src/client.js CHANGED
@@ -5,7 +5,14 @@ const {
5
5
  validateStepUp,
6
6
  } = require("./payerAuth");
7
7
  const { buildStepUpFormHtml } = require("./stepUpForm");
8
- const { authorize, capture, voidPayment, voidCapture } = require("./payments");
8
+ const {
9
+ authorize,
10
+ capture,
11
+ voidPayment,
12
+ voidCapture,
13
+ refundPayment,
14
+ refundCapture,
15
+ } = require("./payments");
9
16
  const { storeInstrument, getInstrument, deleteInstrument } = require("./tms");
10
17
  const { startPayment, finishPayment } = require("./facade");
11
18
  const { lookupBin } = require("./binLookup");
@@ -23,6 +30,8 @@ function createCyberSourceClient(config) {
23
30
  capture: (paymentId, payload) => capture(httpClient, paymentId, payload),
24
31
  voidPayment: (paymentId, payload) => voidPayment(httpClient, paymentId, payload),
25
32
  voidCapture: (captureId, payload) => voidCapture(httpClient, captureId, payload),
33
+ refundPayment: (paymentId, payload) => refundPayment(httpClient, paymentId, payload),
34
+ refundCapture: (captureId, payload) => refundCapture(httpClient, captureId, payload),
26
35
  storeInstrument: (payload) => storeInstrument(httpClient, payload),
27
36
  getInstrument: (payload) => getInstrument(httpClient, payload),
28
37
  deleteInstrument: (payload) => deleteInstrument(httpClient, payload),
package/src/httpClient.js CHANGED
@@ -20,6 +20,43 @@ function createHttpClient({
20
20
  );
21
21
  }
22
22
 
23
+ function extractCybersourceError(parsed, status) {
24
+ if (parsed?.errorInformation) {
25
+ return {
26
+ reason: parsed.errorInformation.reason || "UNKNOWN_ERROR",
27
+
28
+ message:
29
+ parsed.errorInformation.message ||
30
+ `Cybersource request failed with status ${status}`,
31
+
32
+ details: {
33
+ ...parsed.errorInformation,
34
+ transactionId: parsed.id,
35
+ clientReferenceInformation: parsed.clientReferenceInformation,
36
+ status: parsed.status,
37
+ rawResponse: parsed,
38
+ },
39
+ };
40
+ }
41
+
42
+ if (parsed?.errors?.length) {
43
+ return {
44
+ reason: parsed.errors[0]?.reason || "UNKNOWN_ERROR",
45
+ message:
46
+ parsed.errors[0]?.message ||
47
+ `Cybersource request failed with status ${status}`,
48
+ details: parsed.errors,
49
+ };
50
+ }
51
+
52
+ return {
53
+ reason: parsed?.reason || "UNKNOWN_ERROR",
54
+ message:
55
+ parsed?.message || `Cybersource request failed with status ${status}`,
56
+ details: parsed?.details || parsed,
57
+ };
58
+ }
59
+
23
60
  async function request(method, path, body) {
24
61
  const date = new Date().toUTCString();
25
62
  const signatureHeaders = buildSignatureHeaders({
@@ -69,13 +106,13 @@ function createHttpClient({
69
106
  }
70
107
 
71
108
  if (!response.ok) {
109
+ const error = extractCybersourceError(parsed, response.status);
110
+
72
111
  throw new CyberSourceApiError({
73
112
  status: response.status,
74
- reason: parsed.reason || "UNKNOWN_ERROR",
75
- message:
76
- parsed.message ||
77
- `Cybersource request failed with status ${response.status}`,
78
- details: parsed.details || parsed,
113
+ reason: error.reason,
114
+ message: error.message,
115
+ details: error.details,
79
116
  });
80
117
  }
81
118
 
package/src/payments.js CHANGED
@@ -51,11 +51,11 @@ async function authorize(
51
51
  saveCard = false,
52
52
  },
53
53
  ) {
54
- // if (!authenticationResult) {
55
- // throw new Error(
56
- // "authorize requires authenticationResult: no 3-D Secure authentication result was provided",
57
- // );
58
- // }
54
+ if (!authenticationResult) {
55
+ throw new Error(
56
+ "authorize requires authenticationResult: no 3-D Secure authentication result was provided",
57
+ );
58
+ }
59
59
 
60
60
  // Cybersource requires processingInformation.commerceIndicator on every payer-authenticated
61
61
  // authorization — it does not derive it from consumerAuthenticationInformation server-side, and
@@ -228,9 +228,9 @@ async function voidPayment(
228
228
  // Reverses an already-settled capture (POST /pts/v2/captures/{id}/voids), before it reaches the
229
229
  // processor — Cybersource typically batches captures to the processor once a day, so this only
230
230
  // works same-day. Takes the capture()'s own id, not the original authorization's paymentId.
231
- async function voidCapture(
232
- httpClient,
233
- captureId,
231
+ async function voidCapture(
232
+ httpClient,
233
+ captureId,
234
234
  { clientReferenceCode } = {},
235
235
  ) {
236
236
  assertSafeId(captureId, "captureId");
@@ -242,8 +242,57 @@ async function voidCapture(
242
242
  }
243
243
  : undefined,
244
244
  };
245
-
246
- return httpClient.post(`/pts/v2/captures/${captureId}/voids`, body);
247
- }
248
-
249
- module.exports = { authorize, capture, voidPayment, voidCapture };
245
+
246
+ return httpClient.post(`/pts/v2/captures/${captureId}/voids`, body);
247
+ }
248
+
249
+ function buildRefundBody({ currency, totalAmount, clientReferenceCode } = {}) {
250
+ if (!totalAmount || !currency) {
251
+ throw new Error("Refund requires totalAmount and currency");
252
+ }
253
+
254
+ return {
255
+ clientReferenceInformation: clientReferenceCode
256
+ ? {
257
+ code: clientReferenceCode,
258
+ }
259
+ : undefined,
260
+
261
+ orderInformation: {
262
+ amountDetails: {
263
+ totalAmount,
264
+ currency,
265
+ },
266
+ },
267
+ };
268
+ }
269
+
270
+ // Use this only when authorization and capture were combined in authorize(). The id is the
271
+ // payment id returned by that combined operation, as required by Cybersource's payment-scoped
272
+ // refund endpoint.
273
+ async function refundPayment(httpClient, paymentId, payload) {
274
+ assertSafeId(paymentId, "paymentId");
275
+ return httpClient.post(
276
+ `/pts/v2/payments/${paymentId}/refunds`,
277
+ buildRefundBody(payload),
278
+ );
279
+ }
280
+
281
+ // Use this when capture() was called separately. Cybersource requires the capture response's id,
282
+ // not the original authorization's payment id, for this endpoint.
283
+ async function refundCapture(httpClient, captureId, payload) {
284
+ assertSafeId(captureId, "captureId");
285
+ return httpClient.post(
286
+ `/pts/v2/captures/${captureId}/refunds`,
287
+ buildRefundBody(payload),
288
+ );
289
+ }
290
+
291
+ module.exports = {
292
+ authorize,
293
+ capture,
294
+ voidPayment,
295
+ voidCapture,
296
+ refundPayment,
297
+ refundCapture,
298
+ };
@@ -1,7 +1,7 @@
1
1
  // Cybersource's own status vocabulary, spelled out in plain language for people who don't work
2
2
  // with payment processing day to day. Covers every status value documented for the response types
3
3
  // this package returns: authorize() (Payments API), capture(), voidPayment() (Authorization
4
- // Reversal), and voidCapture() (Capture Void).
4
+ // Reversal), voidCapture() (Capture Void), refundPayment(), and refundCapture().
5
5
  const STATUS_DESCRIPTIONS = {
6
6
  AUTHORIZED:
7
7
  "The bank approved the charge. The money is held on the customer's card, but the merchant hasn't taken it yet — that happens on capture.",