@myzonerocks/pact 0.1.3 → 0.1.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.
Files changed (67) hide show
  1. package/dist/src/adapter.d.ts +1 -1
  2. package/dist/src/adapters/erc20.d.ts +13 -3
  3. package/dist/src/adapters/erc20.js +93 -49
  4. package/dist/src/adapters/http.d.ts +2 -0
  5. package/dist/src/adapters/http.js +12 -0
  6. package/dist/src/adapters/mpesa.d.ts +9 -2
  7. package/dist/src/adapters/mpesa.js +82 -12
  8. package/dist/src/adapters/paypal.d.ts +5 -1
  9. package/dist/src/adapters/paypal.js +76 -25
  10. package/dist/src/adapters/stripe.d.ts +3 -2
  11. package/dist/src/adapters/stripe.js +14 -11
  12. package/dist/src/bridge.js +12 -5
  13. package/dist/src/canonical.js +5 -0
  14. package/dist/src/client.d.ts +8 -2
  15. package/dist/src/client.js +181 -20
  16. package/dist/src/compliance.d.ts +4 -0
  17. package/dist/src/compliance.js +10 -3
  18. package/dist/src/crypto.js +4 -1
  19. package/dist/src/index.d.ts +0 -1
  20. package/dist/src/index.js +0 -1
  21. package/dist/src/ledger.d.ts +6 -2
  22. package/dist/src/ledger.js +2 -2
  23. package/dist/src/leg.d.ts +1 -1
  24. package/dist/src/message.d.ts +1 -1
  25. package/dist/src/message.js +8 -5
  26. package/dist/src/money.d.ts +1 -0
  27. package/dist/src/money.js +18 -3
  28. package/dist/src/protocol.d.ts +1 -0
  29. package/dist/src/protocol.js +8 -0
  30. package/dist/src/router.d.ts +2 -0
  31. package/dist/src/router.js +45 -7
  32. package/dist/src/wire.js +19 -2
  33. package/dist/test/erc20.test.js +95 -31
  34. package/dist/test/fake.d.ts +29 -0
  35. package/dist/test/fake.js +79 -0
  36. package/dist/test/lifecycle.test.js +31 -3
  37. package/dist/test/money.test.d.ts +1 -0
  38. package/dist/test/money.test.js +27 -0
  39. package/dist/test/mpesa.test.js +41 -8
  40. package/dist/test/paypal.test.js +33 -8
  41. package/dist/test/policy.test.js +6 -2
  42. package/dist/test/router.test.d.ts +1 -0
  43. package/dist/test/router.test.js +52 -0
  44. package/dist/test/stripe.test.js +5 -4
  45. package/dist/test/vectors.test.js +48 -2
  46. package/dist/test/wire.test.js +15 -0
  47. package/package.json +1 -1
  48. package/src/adapter.ts +7 -2
  49. package/src/adapters/erc20.ts +118 -51
  50. package/src/adapters/http.ts +14 -0
  51. package/src/adapters/mpesa.ts +102 -13
  52. package/src/adapters/paypal.ts +102 -24
  53. package/src/adapters/stripe.ts +16 -13
  54. package/src/bridge.ts +12 -5
  55. package/src/canonical.ts +5 -0
  56. package/src/client.ts +194 -22
  57. package/src/compliance.ts +20 -3
  58. package/src/crypto.ts +4 -1
  59. package/src/index.ts +0 -1
  60. package/src/ledger.ts +12 -4
  61. package/src/leg.ts +4 -1
  62. package/src/message.ts +8 -5
  63. package/src/money.ts +19 -3
  64. package/src/protocol.ts +9 -0
  65. package/src/router.ts +44 -4
  66. package/src/wire.ts +20 -3
  67. package/src/fake.ts +0 -96
@@ -6,6 +6,8 @@
6
6
  // can be refunded in full, but a delivered payout cannot be pulled back.
7
7
  import { State } from "../state.js";
8
8
  import { RefundKind } from "../adapter.js";
9
+ import { idempotencyKey } from "../protocol.js";
10
+ import { fetchWithTimeout } from "./http.js";
9
11
  // The public base of the PayPal REST API. It is the same for every live
10
12
  // integration and carries no secret; the sandbox host or a local test server is
11
13
  // injected instead.
@@ -22,6 +24,9 @@ export class PaypalLeg {
22
24
  api;
23
25
  ids;
24
26
  captures = new Map();
27
+ // What each intent was quoted to collect, so a capture webhook can be
28
+ // cross-checked: the paid amount and currency must match before it settles.
29
+ expected = new Map();
25
30
  constructor(cfg) {
26
31
  this.id = cfg.id ?? "paypal";
27
32
  this.currencies = cfg.currencies;
@@ -33,7 +38,7 @@ export class PaypalLeg {
33
38
  rails: ["paypal"],
34
39
  currencies: this.currencies,
35
40
  methods: ["paypal", "venmo", "card"],
36
- refunds: RefundKind.Full,
41
+ refunds: RefundKind.Partial,
37
42
  };
38
43
  }
39
44
  payOutCapabilities() {
@@ -50,6 +55,10 @@ export class PaypalLeg {
50
55
  referenceId: referencePrefix + intentId,
51
56
  });
52
57
  this.captures.set(intentId, capture.captureId);
58
+ this.expected.set(intentId, {
59
+ value: majorAmount(quote.srcAmount.minor(), quote.srcAmount.exponent),
60
+ currency: quote.srcAmount.currency,
61
+ });
53
62
  return { providerRef: capture.captureId, received: quote.srcAmount.sub(quote.fees) };
54
63
  }
55
64
  // prepare creates an order the payer approves and captures on their own device
@@ -69,6 +78,10 @@ export class PaypalLeg {
69
78
  params.platformFee = majorAmount(quote.fees.minor(), quote.fees.exponent);
70
79
  }
71
80
  const order = await this.api.createOrder(params);
81
+ this.expected.set(intentId, {
82
+ value: majorAmount(quote.srcAmount.minor(), quote.srcAmount.exponent),
83
+ currency: quote.srcAmount.currency,
84
+ });
72
85
  return { providerRef: order.id, clientSecret: order.id, method: "paypal" };
73
86
  }
74
87
  // disburse sends a payout of the recipient's amount to recipientRef and returns
@@ -82,18 +95,23 @@ export class PaypalLeg {
82
95
  });
83
96
  return { providerRef: payout.batchId };
84
97
  }
85
- // refundIn reverses a captured payment in full. PayPal captures the order id in
86
- // the capture id it returned from collect, so the refund binds to that
87
- // reference.
88
- async refundIn(intentId, kind, reason) {
89
- if (kind !== RefundKind.Full) {
98
+ // refundIn reverses a captured payment, in full or in part. PayPal captures the
99
+ // order id in the capture id it returned from collect, so the refund binds to
100
+ // that reference; a partial refund names the amount to return.
101
+ async refundIn(intentId, kind, amount, reason) {
102
+ if (kind !== RefundKind.Full && kind !== RefundKind.Partial) {
90
103
  throw new Error(`paypal: cannot perform refund kind ${kind}`);
91
104
  }
92
105
  const captureId = this.captures.get(intentId);
93
106
  if (!captureId) {
94
107
  throw new Error(`paypal: no capture for intent ${intentId}`);
95
108
  }
96
- const refund = await this.api.refundCapture({ captureId, reason });
109
+ // A partial refund names the amount in major units; a full refund leaves it
110
+ // absent so PayPal returns the whole capture.
111
+ const params = kind === RefundKind.Partial
112
+ ? { captureId, reason, value: majorAmount(amount.minor(), amount.exponent), currencyCode: amount.currency }
113
+ : { captureId, reason };
114
+ const refund = await this.api.refundCapture(params);
97
115
  return {
98
116
  intentId,
99
117
  state: State.Refunded,
@@ -123,8 +141,20 @@ export class PaypalLeg {
123
141
  const intentId = intentFromReference(resource.custom_id ?? "", resource.invoice_id ?? "");
124
142
  const providerRef = resource.id ?? "";
125
143
  switch (event.event_type) {
126
- case "PAYMENT.CAPTURE.COMPLETED":
144
+ case "PAYMENT.CAPTURE.COMPLETED": {
145
+ // The custom_id that ties a capture to an intent is chosen by whoever
146
+ // created the order, so a genuine, signed capture for a different order can
147
+ // carry a target intent's id. Settle only when the captured amount and
148
+ // currency match what the intent was quoted to collect.
149
+ const want = this.expected.get(intentId);
150
+ if (!want) {
151
+ throw new Error(`paypal: capture for unknown intent ${JSON.stringify(intentId)}`);
152
+ }
153
+ if (resource.amount?.value !== want.value || resource.amount?.currency_code !== want.currency) {
154
+ throw new Error(`paypal: captured ${resource.amount?.value} ${resource.amount?.currency_code} does not match the quoted ${want.value} ${want.currency}`);
155
+ }
127
156
  return [oneEvent(intentId, State.Settled, providerRef, "")];
157
+ }
128
158
  case "PAYMENT.CAPTURE.DENIED":
129
159
  return [oneEvent(intentId, State.Failed, providerRef, "capture denied")];
130
160
  case "PAYMENT.CAPTURE.REFUNDED":
@@ -182,7 +212,7 @@ class HttpPaypalApi {
182
212
  return this.token;
183
213
  }
184
214
  const basic = Buffer.from(`${this.creds.clientId}:${this.creds.clientSecret}`).toString("base64");
185
- const resp = await fetch(`${this.baseURL}/v1/oauth2/token`, {
215
+ const resp = await fetchWithTimeout(`${this.baseURL}/v1/oauth2/token`, {
186
216
  method: "POST",
187
217
  headers: { Authorization: `Basic ${basic}`, "Content-Type": "application/x-www-form-urlencoded" },
188
218
  body: "grant_type=client_credentials",
@@ -203,15 +233,12 @@ class HttpPaypalApi {
203
233
  if (params.payee) {
204
234
  unit.payee = { email_address: params.payee };
205
235
  }
206
- const created = await this.postJSON("/v2/checkout/orders", {
207
- intent: "CAPTURE",
208
- purchase_units: [unit],
209
- });
236
+ const created = await this.postJSON("/v2/checkout/orders", { intent: "CAPTURE", purchase_units: [unit] }, idempotencyKey(params.referenceId, "paypal-order"));
210
237
  const existing = firstCapture(created);
211
238
  if (existing) {
212
239
  return { orderId: created.id ?? "", captureId: existing.id ?? "", status: existing.status ?? "" };
213
240
  }
214
- const captured = await this.postJSON(`/v2/checkout/orders/${encodeURIComponent(created.id ?? "")}/capture`, {});
241
+ const captured = await this.postJSON(`/v2/checkout/orders/${encodeURIComponent(created.id ?? "")}/capture`, {}, idempotencyKey(params.referenceId, "paypal-capture"));
215
242
  const capture = firstCapture(captured);
216
243
  if (!capture) {
217
244
  throw new Error(`paypal: order ${created.id} captured without a capture id`);
@@ -235,10 +262,7 @@ class HttpPaypalApi {
235
262
  platform_fees: [{ amount: { currency_code: params.currencyCode, value: params.platformFee } }],
236
263
  };
237
264
  }
238
- const created = await this.postJSON("/v2/checkout/orders", {
239
- intent: "CAPTURE",
240
- purchase_units: [unit],
241
- });
265
+ const created = await this.postJSON("/v2/checkout/orders", { intent: "CAPTURE", purchase_units: [unit] }, idempotencyKey(params.referenceId, "paypal-order"));
242
266
  let approve = "";
243
267
  for (const link of created.links ?? []) {
244
268
  if (link.rel === "approve" || link.rel === "payer-action") {
@@ -267,7 +291,11 @@ class HttpPaypalApi {
267
291
  if (params.reason) {
268
292
  body.note_to_payer = params.reason;
269
293
  }
270
- const out = await this.postJSON(`/v2/payments/captures/${encodeURIComponent(params.captureId)}/refund`, body);
294
+ // A partial refund names the amount; an absent value refunds the full capture.
295
+ if (params.value) {
296
+ body.amount = { value: params.value, currency_code: params.currencyCode };
297
+ }
298
+ const out = await this.postJSON(`/v2/payments/captures/${encodeURIComponent(params.captureId)}/refund`, body, idempotencyKey(params.captureId, "paypal-refund"));
271
299
  return { id: out.id ?? "", status: out.status ?? "" };
272
300
  }
273
301
  // verifyWebhook forwards the signature headers and the raw body to PayPal's
@@ -275,23 +303,46 @@ class HttpPaypalApi {
275
303
  // binds a payload to this integration; PayPal reports whether the signature is
276
304
  // authentic.
277
305
  async verifyWebhook(headers, body) {
278
- const out = await this.postJSON("/v1/notifications/verify-webhook-signature", {
306
+ // PayPal's signature covers the exact bytes of the event it delivered, so the
307
+ // event is forwarded verbatim. Parsing and re-serializing it would reorder
308
+ // keys or restyle numbers and make an authentic webhook fail verification.
309
+ const rawEvent = new TextDecoder().decode(body);
310
+ const sentinel = "__pact_raw_webhook_event__";
311
+ const envelope = JSON.stringify({
279
312
  webhook_id: this.creds.webhookId,
280
313
  transmission_id: header(headers, "PayPal-Transmission-Id"),
281
314
  transmission_time: header(headers, "PayPal-Transmission-Time"),
282
315
  transmission_sig: header(headers, "PayPal-Transmission-Sig"),
283
316
  cert_url: header(headers, "PayPal-Cert-Url"),
284
317
  auth_algo: header(headers, "PayPal-Auth-Algo"),
285
- webhook_event: JSON.parse(new TextDecoder().decode(body)),
318
+ webhook_event: sentinel,
286
319
  });
320
+ const payload = envelope.replace(`"${sentinel}"`, rawEvent);
321
+ const out = await this.postSerialized("/v1/notifications/verify-webhook-signature", payload);
287
322
  return out.verification_status === "SUCCESS";
288
323
  }
289
- async postJSON(path, body) {
324
+ // requestId, when set, is sent as PayPal-Request-Id. PayPal deduplicates a
325
+ // mutation that carries a request id it has already seen, so a retry after a
326
+ // lost response reuses the first order, capture, or refund rather than creating
327
+ // a second.
328
+ async postJSON(path, body, requestId) {
329
+ return this.postSerialized(path, JSON.stringify(body), requestId);
330
+ }
331
+ // postSerialized posts an already-serialized JSON payload, so a caller that must
332
+ // control the exact bytes on the wire — a webhook forwarded verbatim — can do so.
333
+ async postSerialized(path, payload, requestId) {
290
334
  const token = await this.accessToken();
291
- const resp = await fetch(this.baseURL + path, {
335
+ const headers = {
336
+ "Content-Type": "application/json",
337
+ Authorization: `Bearer ${token}`,
338
+ };
339
+ if (requestId) {
340
+ headers["PayPal-Request-Id"] = requestId;
341
+ }
342
+ const resp = await fetchWithTimeout(this.baseURL + path, {
292
343
  method: "POST",
293
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
294
- body: JSON.stringify(body),
344
+ headers,
345
+ body: payload,
295
346
  });
296
347
  if (resp.status >= 300) {
297
348
  throw new Error(`paypal: ${path} returned ${resp.status}`);
@@ -1,3 +1,4 @@
1
+ import { Money } from "../money.js";
1
2
  import type { Quote, Authorization, Settlement } from "../message.js";
2
3
  import { RefundKind, type AdapterEvent } from "../adapter.js";
3
4
  import type { InteractivePayInLeg, PayInCapabilities, CollectResult, PayInPreparation } from "../leg.js";
@@ -45,7 +46,7 @@ export interface StripeConfig {
45
46
  currencies: string[];
46
47
  api: StripeApi;
47
48
  webhookKey: string;
48
- clock?: () => number;
49
+ clock: () => number;
49
50
  tolerance?: number;
50
51
  ids: () => string;
51
52
  methods?: string[];
@@ -63,7 +64,7 @@ export declare class StripeLeg implements InteractivePayInLeg {
63
64
  payInCapabilities(): PayInCapabilities;
64
65
  collect(intentId: string, quote: Quote, _auth: Authorization, deliverTo: string): Promise<CollectResult>;
65
66
  prepare(intentId: string, quote: Quote, _auth: Authorization, deliverTo: string): Promise<PayInPreparation>;
66
- refundIn(intentId: string, kind: RefundKind, reason: string): Promise<Settlement>;
67
+ refundIn(intentId: string, kind: RefundKind, amount: Money, reason: string): Promise<Settlement>;
67
68
  parseWebhook(raw: Uint8Array, headers: Record<string, string[]>): AdapterEvent[];
68
69
  }
69
70
  export declare const ErrNoSignature = "stripe: missing signature header";
@@ -8,6 +8,8 @@
8
8
  import { createHmac, timingSafeEqual } from "node:crypto";
9
9
  import { State } from "../state.js";
10
10
  import { RefundKind } from "../adapter.js";
11
+ import { idempotencyKey } from "../protocol.js";
12
+ import { fetchWithTimeout } from "./http.js";
11
13
  // The public base of the Stripe REST API. It is the same for every integration
12
14
  // and carries no secret; tests point the leg at a local server instead.
13
15
  const defaultBaseURL = "https://api.stripe.com";
@@ -20,8 +22,6 @@ const metadataIntentKey = "pact_intent_id";
20
22
  // defaultToleranceSeconds is how far a webhook timestamp may drift from now
21
23
  // before it is rejected as a possible replay.
22
24
  const defaultToleranceSeconds = 300;
23
- // The protocol identifier used to bind an idempotency key to one protocol step.
24
- const idPrefix = "pact";
25
25
  // Capture selects when the charge is captured. Manual creates the intent already
26
26
  // carrying a payment method for the server to capture immediately — the flow an
27
27
  // agent or a saved-card charge uses. OnConfirmation defers capture to the moment
@@ -48,11 +48,16 @@ export class StripeLeg {
48
48
  if (!cfg.webhookKey) {
49
49
  throw new Error("stripe: config requires a webhook signing key");
50
50
  }
51
+ // Without a clock every webhook timestamp reads as far in the past and
52
+ // silently fails the tolerance check, so a real webhook never verifies.
53
+ if (!cfg.clock) {
54
+ throw new Error("stripe: config requires a clock for webhook timestamp checks");
55
+ }
51
56
  this.id = cfg.id ?? "stripe";
52
57
  this.currencies = cfg.currencies;
53
58
  this.api = cfg.api;
54
59
  this.webhookKey = cfg.webhookKey;
55
- this.clock = cfg.clock ?? (() => 0);
60
+ this.clock = cfg.clock;
56
61
  this.tolerance = cfg.tolerance ?? defaultToleranceSeconds;
57
62
  this.ids = cfg.ids;
58
63
  this.methods = cfg.methods;
@@ -106,12 +111,15 @@ export class StripeLeg {
106
111
  // refundIn reverses a captured payment, in full or in part. Stripe supports
107
112
  // both, so the leg accepts the full and partial refund kinds and rejects a
108
113
  // counter-transfer it cannot express.
109
- async refundIn(intentId, kind, reason) {
114
+ async refundIn(intentId, kind, amount, reason) {
110
115
  if (kind !== RefundKind.Full && kind !== RefundKind.Partial) {
111
116
  throw new Error(`stripe: cannot perform refund kind ${kind}`);
112
117
  }
113
118
  const pi = await this.api.findPaymentIntent(intentId);
114
- const refund = await this.api.createRefund({ paymentIntentId: pi.id, amount: 0, reason }, idempotencyKey(intentId, "refund"));
119
+ // Stripe refunds the full capture when no amount is set, so a full refund
120
+ // leaves amount zero and a partial one carries the exact minor units to return.
121
+ const minor = kind === RefundKind.Partial ? minorToInteger(amount) : 0;
122
+ const refund = await this.api.createRefund({ paymentIntentId: pi.id, amount: minor, reason }, idempotencyKey(intentId, "refund"));
115
123
  return {
116
124
  intentId,
117
125
  state: State.Refunded,
@@ -145,11 +153,6 @@ function signatureHeader(headers) {
145
153
  }
146
154
  return "";
147
155
  }
148
- // idempotencyKey binds a Stripe mutation to one protocol step so a retry reuses
149
- // the original result instead of acting twice.
150
- function idempotencyKey(ref, step) {
151
- return `${idPrefix}:${ref}:${step}`;
152
- }
153
156
  // minorToInteger narrows a Money amount to the safe integer Stripe expects,
154
157
  // refusing anything that would overflow. Fiat amounts fit comfortably; the guard
155
158
  // exists so a token amount can never be sent to a card rail by mistake.
@@ -342,7 +345,7 @@ class HttpStripeApi {
342
345
  Authorization: `Bearer ${this.secretKey}`,
343
346
  "Stripe-Version": apiVersion,
344
347
  };
345
- const resp = await fetch(this.baseURL + path, { ...init, headers });
348
+ const resp = await fetchWithTimeout(this.baseURL + path, { ...init, headers });
346
349
  const body = await resp.text();
347
350
  if (resp.status >= 300) {
348
351
  throw new Error(`stripe: ${path} returned ${resp.status}: ${stripeErrorMessage(body)}`);
@@ -83,14 +83,21 @@ export function applyInverseRate(dst, rate, srcCurrency, srcExponent) {
83
83
  // parseRate reads a decimal rate like "129.45" into a numerator and denominator,
84
84
  // so the conversion stays exact integer arithmetic with no floating point.
85
85
  export function parseRate(rate) {
86
+ // Bound the length so an untrusted rate can't force a huge bigint parse and
87
+ // exponentiation in the FX math.
88
+ if (rate.length > 80) {
89
+ throw new Error("pact: rate has more than 80 characters");
90
+ }
91
+ // A non-negative integer part with no leading zeros and an optional fractional
92
+ // part; no sign, no radix prefix, no whitespace, matching the amount grammar so
93
+ // the SDKs never disagree on a rate's validity or value.
94
+ if (!/^(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(rate)) {
95
+ throw new Error(`pact: ${JSON.stringify(rate)} is not a canonical decimal rate`);
96
+ }
86
97
  const dot = rate.indexOf(".");
87
98
  const whole = dot < 0 ? rate : rate.slice(0, dot);
88
99
  const frac = dot < 0 ? "" : rate.slice(dot + 1);
89
- const digits = whole + frac;
90
- if (!/^\d+$/.test(digits)) {
91
- throw new Error(`pact: ${JSON.stringify(rate)} is not a decimal rate`);
92
- }
93
- const num = BigInt(digits);
100
+ const num = BigInt(whole + frac);
94
101
  const den = frac.length > 0 ? pow10(frac.length) : 1n;
95
102
  return [num, den];
96
103
  }
@@ -20,6 +20,11 @@ export class CanonicalWriter {
20
20
  this.push(b);
21
21
  return this;
22
22
  }
23
+ // str writes a string as its raw UTF-8 bytes. No Unicode normalization is
24
+ // applied: the bytes are hashed as given, so two participants that compose the
25
+ // same text in different Unicode forms produce different hashes. Callers that
26
+ // need them to agree must normalize before building a message; string fields are
27
+ // otherwise treated as opaque bytes.
23
28
  str(s) {
24
29
  return this.bytes(encoder.encode(s));
25
30
  }
@@ -4,9 +4,9 @@ import { type Intent, type Quote, type Authorization, type Settlement } from "./
4
4
  import { type Signer, type Verifier } from "./signing.js";
5
5
  import { type Bridge } from "./bridge.js";
6
6
  import { type PayInLeg, type PayOutLeg, type PayInPreparation } from "./leg.js";
7
- import type { AdapterEvent } from "./adapter.js";
7
+ import { type AdapterEvent } from "./adapter.js";
8
8
  import { type Policy } from "./router.js";
9
- import { type KycProvider, type RiskHook } from "./compliance.js";
9
+ import { type KycProvider, type RiskHook, type SignerAuthorizer } from "./compliance.js";
10
10
  import { type Ledger, type LedgerEvent } from "./ledger.js";
11
11
  export type Clock = () => number;
12
12
  export type IdGen = () => string;
@@ -21,6 +21,7 @@ export interface ClientConfig {
21
21
  verifier: Verifier;
22
22
  kyc?: KycProvider;
23
23
  risk?: RiskHook;
24
+ signerAuth?: SignerAuthorizer;
24
25
  clock?: Clock;
25
26
  idGen?: IdGen;
26
27
  skew?: number;
@@ -49,6 +50,7 @@ export declare class Client {
49
50
  private readonly verifier;
50
51
  private readonly kyc;
51
52
  private readonly risk;
53
+ private readonly signerAuth;
52
54
  private readonly clock;
53
55
  private readonly idGen;
54
56
  private readonly skew;
@@ -75,6 +77,10 @@ export declare class Client {
75
77
  private holdAndDisburse;
76
78
  private settleBridged;
77
79
  private unwind;
80
+ refund(intentId: string, amount: Money, reason: string): Promise<{
81
+ settlement: Settlement;
82
+ state: State;
83
+ }>;
78
84
  private recordFailure;
79
85
  private finishAdvance;
80
86
  private finish;