@@ -9,14 +9,14 @@ import {
99 isPendingGrant ,
1010 createAuthenticatedClient
1111} from '@interledger/open-payments'
12+ import { createId } from '@paralleldrive/cuid2'
1213import { getWalletAddress } from '@shared/utils'
1314import {
1415 createHeaders ,
1516 sleep ,
1617 createHTTPException ,
1718 urlWithParams
1819} from './utils.js'
19- import { createId } from '@paralleldrive/cuid2'
2020import type { Env } from '../app.js'
2121
2222export interface Amount {
@@ -52,6 +52,18 @@ type CreateOutgoingPaymentParams = {
5252 paymentId : string
5353}
5454
55+ const OUTGOING_PAYMENT_POLLING_INITIAL_DELAY = 3000
56+ const OUTGOING_PAYMENT_POLLING_INTERVAL = 1500
57+ const OUTGOING_PAYMENT_POLLING_MAX_ATTEMPTS = 3
58+
59+ function hasCancellationReason ( outgoingPayment : OutgoingPayment ) : boolean {
60+ return (
61+ outgoingPayment . failed &&
62+ typeof outgoingPayment . metadata === 'object' &&
63+ 'cancellationReason' in outgoingPayment . metadata
64+ )
65+ }
66+
5567export class OpenPaymentsService {
5668 private client : AuthenticatedClient | null = null
5769 private static _instance : OpenPaymentsService
@@ -248,11 +260,11 @@ export class OpenPaymentsService {
248260 throw new Error ( 'Could not create outgoing payment.' )
249261 } )
250262
251- return await this . checkOutgoingPayment (
252- outgoingPayment . id ,
253- continuation . access_token . value ,
263+ return await this . completePaymentProcess (
264+ quote . receiver ,
254265 incomingPaymentGrant ,
255- quote . receiver
266+ outgoingPayment . id ,
267+ continuation . access_token . value
256268 )
257269 }
258270
@@ -417,28 +429,46 @@ export class OpenPaymentsService {
417429 }
418430 }
419431
420- private async checkOutgoingPayment (
421- outgoingPaymentId : OutgoingPayment [ 'id' ] ,
422- continuationAccessToken : string ,
432+ private async completePaymentProcess (
433+ incomingPaymentId : string ,
423434 incomingPaymentGrant : Grant ,
424- incomingPaymentId : string
435+ outgoingPaymentId : OutgoingPayment [ 'id' ] ,
436+ continuationAccessToken : string
425437 ) : Promise < CheckPaymentResult > {
426- await sleep ( 3000 )
438+ let attempts = 0
439+ await sleep ( OUTGOING_PAYMENT_POLLING_INITIAL_DELAY )
440+ while ( ++ attempts <= OUTGOING_PAYMENT_POLLING_MAX_ATTEMPTS ) {
441+ const outgoingPayment = await this . client ! . outgoingPayment . get ( {
442+ url : outgoingPaymentId ,
443+ accessToken : continuationAccessToken
444+ } )
427445
428- const outgoingPayment = await this . client ! . outgoingPayment . get ( {
429- url : outgoingPaymentId ,
430- accessToken : continuationAccessToken
431- } )
446+ if ( hasCancellationReason ( outgoingPayment ) ) {
447+ return {
448+ success : false ,
449+ error : {
450+ code : 'CANCELLATION_REASON' ,
451+ message : `Payment aborted due to: ${ outgoingPayment . metadata ?. cancellationReason } `
452+ }
453+ }
454+ }
455+
456+ if (
457+ outgoingPayment . debitAmount . value === outgoingPayment . sentAmount . value
458+ ) {
459+ break
460+ }
432461
433- // get outgoing payment, to check if there was enough balance
434- if ( ! ( Number ( outgoingPayment . sentAmount . value ) > 0 ) ) {
435- return {
436- success : false ,
437- error : {
438- code : 'INSUFFICIENT_BALANCE' ,
439- message : 'Insufficient funds. Check your balance and try again.'
462+ if ( attempts === OUTGOING_PAYMENT_POLLING_MAX_ATTEMPTS ) {
463+ return {
464+ success : false ,
465+ error : {
466+ code : 'OUTGOING_PAYMENT_INCOMPLETE' ,
467+ message : 'The payment did not complete within the expected time.'
468+ }
440469 }
441470 }
471+ await sleep ( OUTGOING_PAYMENT_POLLING_INTERVAL )
442472 }
443473
444474 try {
@@ -458,7 +488,7 @@ export class OpenPaymentsService {
458488 ( error ) => {
459489 throw createHTTPException (
460490 500 ,
461- 'Could not revoke incoming payment grant. ' ,
491+ 'Could not revoke incoming payment grant.' ,
462492 error
463493 )
464494 }
0 commit comments