@bsv/message-box-client 2.0.6 → 2.0.7

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 (32) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/PeerPayClient.js +405 -5
  3. package/dist/cjs/src/PeerPayClient.js.map +1 -1
  4. package/dist/cjs/src/__tests/PeerPayClientRequestIntegration.test.js +317 -0
  5. package/dist/cjs/src/__tests/PeerPayClientRequestIntegration.test.js.map +1 -0
  6. package/dist/cjs/src/__tests/PeerPayClientUnit.test.js +505 -1
  7. package/dist/cjs/src/__tests/PeerPayClientUnit.test.js.map +1 -1
  8. package/dist/cjs/src/types.js +5 -0
  9. package/dist/cjs/src/types.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/PeerPayClient.js +401 -5
  12. package/dist/esm/src/PeerPayClient.js.map +1 -1
  13. package/dist/esm/src/__tests/PeerPayClientRequestIntegration.test.js +312 -0
  14. package/dist/esm/src/__tests/PeerPayClientRequestIntegration.test.js.map +1 -0
  15. package/dist/esm/src/__tests/PeerPayClientUnit.test.js +505 -1
  16. package/dist/esm/src/__tests/PeerPayClientUnit.test.js.map +1 -1
  17. package/dist/esm/src/types.js +4 -1
  18. package/dist/esm/src/types.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/PeerPayClient.d.ts +159 -0
  21. package/dist/types/src/PeerPayClient.d.ts.map +1 -1
  22. package/dist/types/src/__tests/PeerPayClientRequestIntegration.test.d.ts +10 -0
  23. package/dist/types/src/__tests/PeerPayClientRequestIntegration.test.d.ts.map +1 -0
  24. package/dist/types/src/types.d.ts +88 -0
  25. package/dist/types/src/types.d.ts.map +1 -1
  26. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  27. package/dist/umd/bundle.js +1 -1
  28. package/package.json +1 -1
  29. package/src/PeerPayClient.ts +460 -9
  30. package/src/__tests/PeerPayClientRequestIntegration.test.ts +364 -0
  31. package/src/__tests/PeerPayClientUnit.test.ts +594 -1
  32. package/src/types.ts +95 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/message-box-client",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,8 +11,8 @@
11
11
  */
12
12
 
13
13
  import { MessageBoxClient } from './MessageBoxClient.js'
14
- import { PeerMessage } from './types.js'
15
- import { WalletInterface, AtomicBEEF, AuthFetch, Base64String, OriginatorDomainNameStringUnder250Bytes, Brc29RemittanceModule } from '@bsv/sdk'
14
+ import { PeerMessage, PaymentRequestMessage, PaymentRequestResponse, IncomingPaymentRequest, PaymentRequestLimits, DEFAULT_PAYMENT_REQUEST_MIN_AMOUNT, DEFAULT_PAYMENT_REQUEST_MAX_AMOUNT } from './types.js'
15
+ import { WalletInterface, AtomicBEEF, AuthFetch, Base64String, OriginatorDomainNameStringUnder250Bytes, Brc29RemittanceModule, createNonce } from '@bsv/sdk'
16
16
 
17
17
  import * as Logger from './Utils/logger.js'
18
18
 
@@ -20,18 +20,36 @@ function toNumberArray (tx: AtomicBEEF): number[] {
20
20
  return Array.isArray(tx) ? tx : Array.from(tx)
21
21
  }
22
22
 
23
- function safeParse<T> (input: any): T {
23
+ function hexToBytes (hex: string): number[] {
24
+ const matches = hex.match(/.{1,2}/g)
25
+ return matches != null ? matches.map(byte => parseInt(byte, 16)) : []
26
+ }
27
+
28
+ function safeParse<T> (input: any): T | undefined {
24
29
  try {
25
30
  return typeof input === 'string' ? JSON.parse(input) : input
26
31
  } catch (e) {
27
32
  Logger.error('[PP CLIENT] Failed to parse input in safeParse:', input)
28
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
29
- const fallback = {} as T
30
- return fallback
33
+ return undefined
31
34
  }
32
35
  }
33
36
 
37
+ /**
38
+ * Validates that a parsed object has the required fields for a PaymentRequestMessage.
39
+ * Returns true for both new requests (has amount, description, expiresAt) and cancellations (has cancelled: true).
40
+ */
41
+ function isValidPaymentRequestMessage (obj: any): obj is PaymentRequestMessage {
42
+ if (typeof obj !== 'object' || obj === null) return false
43
+ if (typeof obj.requestId !== 'string') return false
44
+ if (typeof obj.senderIdentityKey !== 'string') return false
45
+ if (typeof obj.requestProof !== 'string') return false
46
+ if (obj.cancelled === true) return true
47
+ return typeof obj.amount === 'number' && typeof obj.description === 'string' && typeof obj.expiresAt === 'number'
48
+ }
49
+
34
50
  export const STANDARD_PAYMENT_MESSAGEBOX = 'payment_inbox'
51
+ export const PAYMENT_REQUESTS_MESSAGEBOX = 'payment_requests'
52
+ export const PAYMENT_REQUEST_RESPONSES_MESSAGEBOX = 'payment_request_responses'
35
53
  const STANDARD_PAYMENT_OUTPUT_INDEX = 0
36
54
 
37
55
  /**
@@ -113,6 +131,59 @@ export class PeerPayClient extends MessageBoxClient {
113
131
  return this._authFetchInstance
114
132
  }
115
133
 
134
+ /**
135
+ * Allows payment requests from a specific identity key by setting
136
+ * the recipientFee to 0 for the payment_requests message box.
137
+ *
138
+ * @param {Object} params - Parameters.
139
+ * @param {string} params.identityKey - The identity key to allow payment requests from.
140
+ * @returns {Promise<void>} Resolves when the permission is set.
141
+ */
142
+ async allowPaymentRequestsFrom ({ identityKey }: { identityKey: string }): Promise<void> {
143
+ await this.setMessageBoxPermission({
144
+ messageBox: PAYMENT_REQUESTS_MESSAGEBOX,
145
+ sender: identityKey,
146
+ recipientFee: 0
147
+ })
148
+ }
149
+
150
+ /**
151
+ * Blocks payment requests from a specific identity key by setting
152
+ * the recipientFee to -1 for the payment_requests message box.
153
+ *
154
+ * @param {Object} params - Parameters.
155
+ * @param {string} params.identityKey - The identity key to block payment requests from.
156
+ * @returns {Promise<void>} Resolves when the permission is set.
157
+ */
158
+ async blockPaymentRequestsFrom ({ identityKey }: { identityKey: string }): Promise<void> {
159
+ await this.setMessageBoxPermission({
160
+ messageBox: PAYMENT_REQUESTS_MESSAGEBOX,
161
+ sender: identityKey,
162
+ recipientFee: -1
163
+ })
164
+ }
165
+
166
+ /**
167
+ * Lists all permissions for the payment_requests message box, mapped to
168
+ * a simplified { identityKey, allowed } structure.
169
+ *
170
+ * A permission is considered "allowed" if recipientFee >= 0 (0 = always allow,
171
+ * positive = payment required). A recipientFee of -1 means blocked.
172
+ *
173
+ * @returns {Promise<Array<{ identityKey: string, allowed: boolean }>>} Resolved with the list of permissions.
174
+ */
175
+ async listPaymentRequestPermissions (): Promise<Array<{ identityKey: string, allowed: boolean }>> {
176
+ const permissions = await this.listMessageBoxPermissions({ messageBox: PAYMENT_REQUESTS_MESSAGEBOX })
177
+ // Filter to only per-sender entries (sender is not null/empty).
178
+ // Use the status field returned by the server to determine allowed state.
179
+ return permissions
180
+ .filter(p => p.sender != null && p.sender !== '')
181
+ .map(p => ({
182
+ identityKey: p.sender ?? '',
183
+ allowed: p.status !== 'blocked'
184
+ }))
185
+ }
186
+
116
187
  /**
117
188
  * Generates a valid payment token for a recipient.
118
189
  *
@@ -159,8 +230,8 @@ export class PeerPayClient extends MessageBoxClient {
159
230
 
160
231
  return {
161
232
  customInstructions: {
162
- derivationPrefix: result.artifact.customInstructions.derivationPrefix as Base64String,
163
- derivationSuffix: result.artifact.customInstructions.derivationSuffix as Base64String
233
+ derivationPrefix: result.artifact.customInstructions.derivationPrefix,
234
+ derivationSuffix: result.artifact.customInstructions.derivationSuffix
164
235
  },
165
236
  transaction: result.artifact.transaction as AtomicBEEF,
166
237
  amount: result.artifact.amountSatoshis
@@ -257,10 +328,12 @@ export class PeerPayClient extends MessageBoxClient {
257
328
  // Convert PeerMessage → IncomingPayment before calling onPayment
258
329
  onMessage: (message: PeerMessage) => {
259
330
  Logger.log('[MB CLIENT] Received Live Payment:', message)
331
+ const token = safeParse<PaymentToken>(message.body)
332
+ if (token == null) return
260
333
  const incomingPayment: IncomingPayment = {
261
334
  messageId: message.messageId,
262
335
  sender: message.sender,
263
- token: safeParse<PaymentToken>(message.body)
336
+ token
264
337
  }
265
338
  Logger.log('[PP CLIENT] Converted PeerMessage to IncomingPayment:', incomingPayment)
266
339
  onPayment(incomingPayment)
@@ -398,12 +471,390 @@ export class PeerPayClient extends MessageBoxClient {
398
471
  const messages = await this.listMessages({ messageBox: this.messageBox, host: overrideHost })
399
472
  return messages.map((msg: any) => {
400
473
  const parsedToken = safeParse<PaymentToken>(msg.body)
474
+ if (parsedToken == null) return null
401
475
 
402
476
  return {
403
477
  messageId: msg.messageId,
404
478
  sender: msg.sender,
405
479
  token: parsedToken
406
480
  }
481
+ }).filter((p): p is IncomingPayment => p != null)
482
+ }
483
+
484
+ /**
485
+ * Lists all responses to payment requests from the payment_request_responses message box.
486
+ *
487
+ * Retrieves messages and parses each as a PaymentRequestResponse.
488
+ *
489
+ * @param {string} [hostOverride] - Optional host override for the message box server.
490
+ * @returns {Promise<PaymentRequestResponse[]>} Resolves with an array of payment request responses.
491
+ */
492
+ async listPaymentRequestResponses (hostOverride?: string): Promise<PaymentRequestResponse[]> {
493
+ const messages = await this.listMessages({ messageBox: PAYMENT_REQUEST_RESPONSES_MESSAGEBOX, host: hostOverride })
494
+ return messages.map((msg: any) => safeParse<PaymentRequestResponse>(msg.body))
495
+ .filter((r): r is PaymentRequestResponse => r != null)
496
+ }
497
+
498
+ /**
499
+ * Listens for incoming payment requests in real time via WebSocket.
500
+ *
501
+ * Wraps listenForLiveMessages on the payment_requests box and converts each
502
+ * incoming PeerMessage into an IncomingPaymentRequest before calling onRequest.
503
+ *
504
+ * @param {Object} params - Listener configuration.
505
+ * @param {Function} params.onRequest - Callback invoked when a new payment request arrives.
506
+ * @param {string} [params.overrideHost] - Optional host override for the WebSocket connection.
507
+ * @returns {Promise<void>} Resolves when the listener is established.
508
+ */
509
+ async listenForLivePaymentRequests ({
510
+ onRequest,
511
+ overrideHost
512
+ }: {
513
+ onRequest: (request: IncomingPaymentRequest) => void
514
+ overrideHost?: string
515
+ }): Promise<void> {
516
+ await this.listenForLiveMessages({
517
+ messageBox: PAYMENT_REQUESTS_MESSAGEBOX,
518
+ overrideHost,
519
+ onMessage: (message: PeerMessage) => {
520
+ const body = safeParse<PaymentRequestMessage>(message.body)
521
+ if (body == null || body.cancelled === true) return // Skip cancellations and parse failures
522
+ const request: IncomingPaymentRequest = {
523
+ messageId: message.messageId,
524
+ sender: message.sender,
525
+ requestId: body.requestId,
526
+ amount: body.amount,
527
+ description: body.description,
528
+ expiresAt: body.expiresAt
529
+ }
530
+ onRequest(request)
531
+ }
407
532
  })
408
533
  }
534
+
535
+ /**
536
+ * Listens for payment request responses in real time via WebSocket.
537
+ *
538
+ * Wraps listenForLiveMessages on the payment_request_responses box and converts each
539
+ * incoming PeerMessage into a PaymentRequestResponse before calling onResponse.
540
+ *
541
+ * @param {Object} params - Listener configuration.
542
+ * @param {Function} params.onResponse - Callback invoked when a new response arrives.
543
+ * @param {string} [params.overrideHost] - Optional host override for the WebSocket connection.
544
+ * @returns {Promise<void>} Resolves when the listener is established.
545
+ */
546
+ async listenForLivePaymentRequestResponses ({
547
+ onResponse,
548
+ overrideHost
549
+ }: {
550
+ onResponse: (response: PaymentRequestResponse) => void
551
+ overrideHost?: string
552
+ }): Promise<void> {
553
+ await this.listenForLiveMessages({
554
+ messageBox: PAYMENT_REQUEST_RESPONSES_MESSAGEBOX,
555
+ overrideHost,
556
+ onMessage: (message: PeerMessage) => {
557
+ const response = safeParse<PaymentRequestResponse>(message.body)
558
+ if (response == null) return
559
+ onResponse(response)
560
+ }
561
+ })
562
+ }
563
+
564
+ /**
565
+ * Fulfills an incoming payment request by sending the requested payment and
566
+ * notifying the requester with a 'paid' response in the payment_request_responses box.
567
+ * Also acknowledges the original request message.
568
+ *
569
+ * @param {Object} params - Fulfillment parameters.
570
+ * @param {IncomingPaymentRequest} params.request - The incoming payment request to fulfill.
571
+ * @param {string} [params.note] - Optional note to include in the response.
572
+ * @param {string} [hostOverride] - Optional host override for the message box server.
573
+ * @returns {Promise<void>} Resolves when payment is sent and acknowledgment is complete.
574
+ */
575
+ async fulfillPaymentRequest (
576
+ params: { request: IncomingPaymentRequest, note?: string },
577
+ hostOverride?: string
578
+ ): Promise<void> {
579
+ const { request, note } = params
580
+
581
+ await this.sendPayment({ recipient: request.sender, amount: request.amount }, hostOverride)
582
+
583
+ const response: PaymentRequestResponse = {
584
+ requestId: request.requestId,
585
+ status: 'paid',
586
+ amountPaid: request.amount,
587
+ ...(note != null && { note })
588
+ }
589
+
590
+ await this.sendMessage({
591
+ recipient: request.sender,
592
+ messageBox: PAYMENT_REQUEST_RESPONSES_MESSAGEBOX,
593
+ body: JSON.stringify(response)
594
+ }, hostOverride)
595
+
596
+ await this.acknowledgeMessage({ messageIds: [request.messageId], host: hostOverride })
597
+ }
598
+
599
+ /**
600
+ * Declines an incoming payment request by notifying the requester with a 'declined'
601
+ * response in the payment_request_responses box and acknowledging the original request.
602
+ *
603
+ * @param {Object} params - Decline parameters.
604
+ * @param {IncomingPaymentRequest} params.request - The incoming payment request to decline.
605
+ * @param {string} [params.note] - Optional note explaining why the request was declined.
606
+ * @param {string} [hostOverride] - Optional host override for the message box server.
607
+ * @returns {Promise<void>} Resolves when the response is sent and request is acknowledged.
608
+ */
609
+ async declinePaymentRequest (
610
+ params: { request: IncomingPaymentRequest, note?: string },
611
+ hostOverride?: string
612
+ ): Promise<void> {
613
+ const { request, note } = params
614
+
615
+ const response: PaymentRequestResponse = {
616
+ requestId: request.requestId,
617
+ status: 'declined',
618
+ ...(note != null && { note })
619
+ }
620
+
621
+ await this.sendMessage({
622
+ recipient: request.sender,
623
+ messageBox: PAYMENT_REQUEST_RESPONSES_MESSAGEBOX,
624
+ body: JSON.stringify(response)
625
+ }, hostOverride)
626
+
627
+ await this.acknowledgeMessage({ messageIds: [request.messageId], host: hostOverride })
628
+ }
629
+
630
+ /**
631
+ * Sends a payment request to a recipient via the payment_requests message box.
632
+ *
633
+ * Generates a unique requestId using createNonce, looks up the caller's identity key,
634
+ * and sends a PaymentRequestMessage to the recipient.
635
+ *
636
+ * @param {Object} params - Payment request parameters.
637
+ * @param {string} params.recipient - The identity key of the intended payer.
638
+ * @param {number} params.amount - The amount in satoshis being requested (must be > 0).
639
+ * @param {string} params.description - Human-readable reason for the payment request.
640
+ * @param {number} params.expiresAt - Unix timestamp (ms) when the request expires.
641
+ * @param {string} [hostOverride] - Optional host override for the message box server.
642
+ * @returns {Promise<{ requestId: string }>} The generated requestId for this request.
643
+ * @throws {Error} If amount is <= 0.
644
+ */
645
+ async requestPayment (
646
+ params: { recipient: string, amount: number, description: string, expiresAt: number },
647
+ hostOverride?: string
648
+ ): Promise<{ requestId: string, requestProof: string }> {
649
+ if (params.amount <= 0) {
650
+ throw new Error('Invalid payment request: amount must be greater than 0')
651
+ }
652
+
653
+ const requestId = await createNonce(this.peerPayWalletClient, 'self', this.originator)
654
+ const senderIdentityKey = await this.getIdentityKey()
655
+
656
+ const proofData = Array.from(new TextEncoder().encode(requestId + params.recipient))
657
+ const { hmac } = await this.peerPayWalletClient.createHmac({
658
+ data: proofData,
659
+ protocolID: [2, 'payment request auth'],
660
+ keyID: requestId,
661
+ counterparty: params.recipient
662
+ }, this.originator)
663
+ const requestProof = Array.from(hmac).map(b => b.toString(16).padStart(2, '0')).join('')
664
+
665
+ const body: PaymentRequestMessage = {
666
+ requestId,
667
+ amount: params.amount,
668
+ description: params.description,
669
+ expiresAt: params.expiresAt,
670
+ senderIdentityKey,
671
+ requestProof
672
+ }
673
+
674
+ try {
675
+ await this.sendMessage({
676
+ recipient: params.recipient,
677
+ messageBox: PAYMENT_REQUESTS_MESSAGEBOX,
678
+ body: JSON.stringify(body)
679
+ }, hostOverride)
680
+ } catch (err: any) {
681
+ // Translate HTTP 403 (permission denied) into a user-friendly message.
682
+ if (typeof err?.message === 'string' && err.message.includes('403')) {
683
+ throw new Error('Payment request blocked — you are not on the recipient\'s whitelist.')
684
+ }
685
+ throw err
686
+ }
687
+
688
+ return { requestId, requestProof }
689
+ }
690
+
691
+ /**
692
+ * Lists all incoming payment requests from the payment_requests message box.
693
+ *
694
+ * Automatically filters out:
695
+ * - Expired requests (expiresAt < now), which are acknowledged and discarded.
696
+ * - Cancelled requests (a cancellation message with the same requestId exists),
697
+ * both the original and cancellation messages are acknowledged and discarded.
698
+ * - Out-of-range requests (when limits are provided), which are acknowledged and discarded.
699
+ *
700
+ * @param {string} [hostOverride] - Optional host override for the message box server.
701
+ * @param {PaymentRequestLimits} [limits] - Optional min/max satoshi limits for filtering.
702
+ * @returns {Promise<IncomingPaymentRequest[]>} Resolves with active, valid payment requests.
703
+ */
704
+ async listIncomingPaymentRequests (
705
+ hostOverride?: string,
706
+ limits?: PaymentRequestLimits
707
+ ): Promise<IncomingPaymentRequest[]> {
708
+ const messages = await this.listMessages({ messageBox: PAYMENT_REQUESTS_MESSAGEBOX, host: hostOverride })
709
+ const myIdentityKey = await this.getIdentityKey()
710
+ const now = Date.now()
711
+
712
+ // Parse and validate all messages, collecting malformed ones for ack
713
+ const malformedMessageIds: string[] = []
714
+ const parsed: Array<{ messageId: string, sender: string, body: PaymentRequestMessage }> = []
715
+
716
+ for (const msg of messages) {
717
+ const body = safeParse<PaymentRequestMessage>(msg.body)
718
+ if (body != null && isValidPaymentRequestMessage(body)) {
719
+ parsed.push({ messageId: msg.messageId as string, sender: msg.sender as string, body })
720
+ } else {
721
+ malformedMessageIds.push(msg.messageId as string)
722
+ }
723
+ }
724
+
725
+ // Collect cancelled requestIds — verify HMAC proof before accepting
726
+ const cancelledRequests = new Map<string, string>() // requestId → sender
727
+ const cancelMessageIds: string[] = []
728
+ for (const item of parsed) {
729
+ if (item.body.cancelled === true) {
730
+ // Verify cancellation HMAC proof
731
+ try {
732
+ const proofData = Array.from(new TextEncoder().encode(item.body.requestId + myIdentityKey))
733
+ await this.peerPayWalletClient.verifyHmac({
734
+ data: proofData,
735
+ hmac: hexToBytes(item.body.requestProof),
736
+ protocolID: [2, 'payment request auth'],
737
+ keyID: item.body.requestId,
738
+ counterparty: item.sender
739
+ }, this.originator)
740
+ cancelledRequests.set(item.body.requestId, item.sender)
741
+ cancelMessageIds.push(item.messageId)
742
+ } catch {
743
+ Logger.warn(`[PP CLIENT] Invalid cancellation proof for requestId=${item.body.requestId}, discarding`)
744
+ malformedMessageIds.push(item.messageId)
745
+ }
746
+ continue
747
+ }
748
+ }
749
+
750
+ const expiredMessageIds: string[] = []
751
+ const outOfRangeMessageIds: string[] = []
752
+ const cancelledOriginalMessageIds: string[] = []
753
+ const active: IncomingPaymentRequest[] = []
754
+
755
+ for (const item of parsed) {
756
+ // Skip cancellation messages themselves (already collected above)
757
+ if (item.body.cancelled === true) continue
758
+
759
+ const { requestId, amount, description, expiresAt } = item.body
760
+
761
+ // Filter expired
762
+ if (expiresAt < now) {
763
+ expiredMessageIds.push(item.messageId)
764
+ continue
765
+ }
766
+
767
+ // Filter cancelled originals — only if cancellation came from the same sender
768
+ if (cancelledRequests.has(requestId) && cancelledRequests.get(requestId) === item.sender) {
769
+ cancelledOriginalMessageIds.push(item.messageId)
770
+ continue
771
+ }
772
+
773
+ // Filter out-of-range — apply defaults for any missing limit fields
774
+ const effectiveMin = limits?.minAmount ?? DEFAULT_PAYMENT_REQUEST_MIN_AMOUNT
775
+ const effectiveMax = limits?.maxAmount ?? DEFAULT_PAYMENT_REQUEST_MAX_AMOUNT
776
+ if (amount < effectiveMin || amount > effectiveMax) {
777
+ outOfRangeMessageIds.push(item.messageId)
778
+ continue
779
+ }
780
+
781
+ // Verify HMAC proof — ensures message came from claimed sender
782
+ try {
783
+ const proofData = Array.from(new TextEncoder().encode(requestId + myIdentityKey))
784
+ await this.peerPayWalletClient.verifyHmac({
785
+ data: proofData,
786
+ hmac: hexToBytes(item.body.requestProof),
787
+ protocolID: [2, 'payment request auth'],
788
+ keyID: requestId,
789
+ counterparty: item.sender
790
+ }, this.originator)
791
+ } catch {
792
+ Logger.warn(`[PP CLIENT] Invalid requestProof for requestId=${requestId}, discarding`)
793
+ malformedMessageIds.push(item.messageId)
794
+ continue
795
+ }
796
+
797
+ active.push({
798
+ messageId: item.messageId,
799
+ sender: item.sender,
800
+ requestId,
801
+ amount,
802
+ description,
803
+ expiresAt
804
+ })
805
+ }
806
+
807
+ // Acknowledge expired
808
+ if (expiredMessageIds.length > 0) {
809
+ await this.acknowledgeMessage({ messageIds: expiredMessageIds, host: hostOverride })
810
+ }
811
+
812
+ // Acknowledge cancelled originals + cancel messages together
813
+ const cancelAckIds = [...cancelledOriginalMessageIds, ...cancelMessageIds]
814
+ if (cancelAckIds.length > 0) {
815
+ await this.acknowledgeMessage({ messageIds: cancelAckIds, host: hostOverride })
816
+ }
817
+
818
+ // Acknowledge out-of-range
819
+ if (outOfRangeMessageIds.length > 0) {
820
+ await this.acknowledgeMessage({ messageIds: outOfRangeMessageIds, host: hostOverride })
821
+ }
822
+
823
+ // Acknowledge malformed messages so they don't reappear
824
+ if (malformedMessageIds.length > 0) {
825
+ await this.acknowledgeMessage({ messageIds: malformedMessageIds, host: hostOverride })
826
+ }
827
+
828
+ return active
829
+ }
830
+
831
+ /**
832
+ * Cancels a previously sent payment request by sending a cancellation message
833
+ * with the same requestId and `cancelled: true`.
834
+ *
835
+ * @param {Object} params - Cancellation parameters.
836
+ * @param {string} params.recipient - The identity key of the recipient of the original request.
837
+ * @param {string} params.requestId - The requestId of the payment request to cancel.
838
+ * @param {string} [hostOverride] - Optional host override for the message box server.
839
+ * @returns {Promise<void>} Resolves when the cancellation message has been sent.
840
+ */
841
+ async cancelPaymentRequest (
842
+ params: { recipient: string, requestId: string, requestProof: string },
843
+ hostOverride?: string
844
+ ): Promise<void> {
845
+ const senderIdentityKey = await this.getIdentityKey()
846
+
847
+ const body: PaymentRequestMessage = {
848
+ requestId: params.requestId,
849
+ senderIdentityKey,
850
+ requestProof: params.requestProof,
851
+ cancelled: true
852
+ }
853
+
854
+ await this.sendMessage({
855
+ recipient: params.recipient,
856
+ messageBox: PAYMENT_REQUESTS_MESSAGEBOX,
857
+ body: JSON.stringify(body)
858
+ }, hostOverride)
859
+ }
409
860
  }