cybersource-brunei-payauth 0.2.0 → 0.2.2

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
@@ -224,6 +224,8 @@ If you need to resolve this properly: reproduce the same request in Cybersource'
224
224
 
225
225
  Cybersource splits "cancel this payment" into two different real operations depending on whether it's been captured yet, each against a *different* id — the original authorization's `paymentId` vs. the capture's own `id` from `capture()`'s response. `checkTransaction` tells you which stage a payment is at (and the exact amount a reversal must match); `voidTransaction` uses it to pick the right operation for you:
226
226
 
227
+ > **`checkTransaction`/`voidTransaction` can 404 for several minutes after a payment is created.** They read from Cybersource's Transaction Search index, which is separate from (and lags behind) the Payments API — a `CyberSourceApiError` with `status: 404` shortly after `authorize()`/`capture()` usually means the index hasn't caught up yet, not that the transaction doesn't exist. Cybersource's own guidance is to retry every 5 minutes, up to 5 times; this package rewrites that 404's message to say so, but doesn't retry for you. **If you already know the payment hasn't been captured yet** — the common case of cancelling immediately after checkout, before ever calling `capture()` — skip `checkTransaction`/`voidTransaction` entirely and call `voidPayment` directly with the amount from your own `authorize()` response; it doesn't depend on the Transaction Search index at all.
228
+
227
229
  ```js
228
230
  const outcome = await client.voidTransaction(paymentId, { reason: 'customer cancelled' });
229
231
  // outcome: { operation: 'reversal' | 'capture-void', result: <Cybersource response>, checked: <checkTransaction's result> }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cybersource-brunei-payauth",
3
- "version": "0.2.0",
4
- "description": "Cybersource payer-authentication (3-D Secure) + payments client for Brunei merchants. Adds a startPayment/finishPayment facade, BIN lookup, authorization/capture voiding (checkTransaction/voidTransaction/voidPayment/voidCapture), and plain-language status descriptions (describeStatus).",
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.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
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
@@ -67,14 +67,21 @@ async function authorize(
67
67
  (paymentInstrument &&
68
68
  paymentInstrument.transientToken &&
69
69
  extractCardType(paymentInstrument.transientToken)) ||
70
- authenticationResult.cardBrand;
71
- const commerceIndicator =
72
- authenticationResult.commerceIndicator ||
73
- mapCommerceIndicator({ cardBrand, eciRaw: authenticationResult.eciRaw });
74
-
75
- const processingInformation = {
76
- commerceIndicator,
77
- };
70
+ authenticationResult?.cardBrand;
71
+
72
+ const commerceIndicator = authenticationResult
73
+ ? authenticationResult.commerceIndicator ||
74
+ mapCommerceIndicator({
75
+ cardBrand,
76
+ eciRaw: authenticationResult.eciRaw,
77
+ })
78
+ : undefined;
79
+
80
+ const processingInformation = commerceIndicator
81
+ ? {
82
+ commerceIndicator,
83
+ }
84
+ : undefined;
78
85
 
79
86
  // Requesting a "customer" profile token alongside TOKEN_CREATE is what caused every previous
80
87
  // attempt at this to be rejected: customer-profile creation is a separately provisioned TMS vault
@@ -119,8 +126,9 @@ async function authorize(
119
126
  billTo,
120
127
  },
121
128
 
122
- consumerAuthenticationInformation:
123
- buildConsumerAuthenticationInformation(authenticationResult),
129
+ consumerAuthenticationInformation: authenticationResult
130
+ ? buildConsumerAuthenticationInformation(authenticationResult)
131
+ : undefined,
124
132
  };
125
133
 
126
134
  console.log("=== FULL CYBERSOURCE AUTHORIZE REQUEST ===");
@@ -19,6 +19,10 @@ const STATUS_DESCRIPTIONS = {
19
19
  "The request itself was missing something or malformed — the bank was never actually asked to approve anything.",
20
20
  PENDING:
21
21
  "Accepted and queued to be sent onward for processing; the final result isn't back yet.",
22
+ CAPTURE_PENDING:
23
+ "The merchant has claimed the money and it's queued for the next scheduled settlement batch to the bank — a capture can still be voided while it's in this state.",
24
+ CAPTURED:
25
+ "The merchant has successfully claimed the money that was on hold from the authorization.",
22
26
  TRANSMITTED: "Sent to the bank for processing (this is what a capture's status becomes once submitted).",
23
27
  REVERSED:
24
28
  "A charge that had been approved but not yet taken was cancelled — the hold on the customer's card was released, as if it never happened.",
@@ -27,7 +27,29 @@ function extractRelatedTransactionIds(response) {
27
27
  async function checkTransaction(httpClient, transactionId) {
28
28
  assertSafeId(transactionId, "transactionId");
29
29
 
30
- const response = await httpClient.get(`/tss/v2/transactions/${transactionId}`);
30
+ let response;
31
+ try {
32
+ response = await httpClient.get(`/tss/v2/transactions/${transactionId}`);
33
+ } catch (err) {
34
+ // Cybersource's Transaction Search index lags behind the Payments API by anywhere from
35
+ // seconds to several minutes — a 404 here very often means "not indexed yet", not "this
36
+ // transaction doesn't exist". Cybersource's own documented guidance is to retry every 5
37
+ // minutes, up to 5 times. Rethrow the same error (preserving status/reason/details for
38
+ // programmatic handling) with a message that says so, instead of leaving callers to
39
+ // conclude their transaction id was wrong.
40
+ if (err && err.status === 404) {
41
+ err.message =
42
+ `checkTransaction: Cybersource returned 404 for transaction "${transactionId}" — this usually ` +
43
+ "means its Transaction Search index hasn't caught up yet, not that the transaction doesn't " +
44
+ "exist (Cybersource's own guidance: retry every 5 minutes, up to 5 times). If you already know " +
45
+ "this authorization hasn't been captured yet (e.g. you're voiding it immediately after " +
46
+ "authorize(), before ever calling capture()), you don't need to wait on this at all — call " +
47
+ "voidPayment directly with the amount from your own authorize() response instead of going " +
48
+ "through checkTransaction/voidTransaction.";
49
+ }
50
+ throw err;
51
+ }
52
+
31
53
  const info = response.applicationInformation || {};
32
54
  const amounts = (response.orderInformation && response.orderInformation.amountDetails) || {};
33
55