Skip to content

Commit 7d8079f

Browse files
cozminumkurapov
andauthored
feat(backend): return minSendAmount in quote responses (#3411)
* feat(backend): change error response * feat(backend): send minSendAmount * feat(backend): return minSendAmount if debitAmount is too small * Apply commit suggestions Co-authored-by: Max Kurapov <max@interledger.org> * feat(backend): fix calculation for minSendAmount * feat(backend): improved minSendAmount calculation * feat(backend): minSendAmount for local payments * fix(backend): integration tests * fix(backend): minSendAmount using ratio reciprocal Co-authored-by: Max Kurapov <max@interledger.org> * feat(backend): add error details to graphql reponse * fix(backend): formatting * fix(backend): minSendAmount as BigInt * fix(backend): return minSendAmount * feat(backend): add test for minDeliveryAmount * fix(backend): lint fix --------- Co-authored-by: Max Kurapov <max@interledger.org>
1 parent e6b01e7 commit 7d8079f

20 files changed

Lines changed: 483 additions & 128 deletions

File tree

packages/backend/src/graphql/resolvers/quote.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { createWalletAddress } from '../../tests/walletAddress'
1313
import { createQuote } from '../../tests/quote'
1414
import { truncateTables } from '../../tests/tableManager'
1515
import {
16-
QuoteError,
1716
errorToMessage,
18-
errorToCode
17+
errorToCode,
18+
QuoteErrorCode
1919
} from '../../open_payments/quote/errors'
2020
import { QuoteService } from '../../open_payments/quote/service'
2121
import { Quote as QuoteModel } from '../../open_payments/quote/model'
@@ -249,9 +249,9 @@ describe('Quote Resolvers', (): void => {
249249
expect(error).toBeInstanceOf(ApolloError)
250250
expect((error as ApolloError).graphQLErrors).toContainEqual(
251251
expect.objectContaining({
252-
message: errorToMessage[QuoteError.UnknownWalletAddress],
252+
message: errorToMessage[QuoteErrorCode.UnknownWalletAddress],
253253
extensions: expect.objectContaining({
254-
code: errorToCode[QuoteError.UnknownWalletAddress]
254+
code: errorToCode[QuoteErrorCode.UnknownWalletAddress]
255255
})
256256
})
257257
)

packages/backend/src/graphql/resolvers/quote.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ export const createQuote: MutationResolvers<ApolloContext>['createQuote'] =
5050
options.receiveAmount = args.input.receiveAmount
5151
const quoteOrError = await quoteService.create(options)
5252
if (isQuoteError(quoteOrError)) {
53-
throw new GraphQLError(errorToMessage[quoteOrError], {
53+
throw new GraphQLError(errorToMessage[quoteOrError.type], {
5454
extensions: {
55-
code: errorToCode[quoteOrError]
55+
code: errorToCode[quoteOrError.type],
56+
details: quoteOrError.details
5657
}
5758
})
5859
} else

packages/backend/src/open_payments/payment/outgoing/errors.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '../../../accounting/errors'
66
import { GraphQLErrorCode } from '../../../graphql/errors'
77
import { PaymentMethodHandlerError } from '../../../payment-method/handler/errors'
8-
import { QuoteError } from '../../quote/errors'
8+
import { QuoteErrorCode } from '../../quote/errors'
99

1010
export enum OutgoingPaymentError {
1111
UnknownWalletAddress = 'UnknownWalletAddress',
@@ -21,15 +21,16 @@ export enum OutgoingPaymentError {
2121
}
2222

2323
export const quoteErrorToOutgoingPaymentError: Record<
24-
QuoteError,
24+
QuoteErrorCode,
2525
OutgoingPaymentError
2626
> = {
27-
[QuoteError.UnknownWalletAddress]: OutgoingPaymentError.UnknownWalletAddress,
28-
[QuoteError.InvalidAmount]: OutgoingPaymentError.InvalidAmount,
29-
[QuoteError.InvalidReceiver]: OutgoingPaymentError.InvalidReceiver,
30-
[QuoteError.InactiveWalletAddress]:
27+
[QuoteErrorCode.UnknownWalletAddress]:
28+
OutgoingPaymentError.UnknownWalletAddress,
29+
[QuoteErrorCode.InvalidAmount]: OutgoingPaymentError.InvalidAmount,
30+
[QuoteErrorCode.InvalidReceiver]: OutgoingPaymentError.InvalidReceiver,
31+
[QuoteErrorCode.InactiveWalletAddress]:
3132
OutgoingPaymentError.InactiveWalletAddress,
32-
[QuoteError.NonPositiveReceiveAmount]:
33+
[QuoteErrorCode.NonPositiveReceiveAmount]:
3334
OutgoingPaymentError.NegativeReceiveAmount
3435
}
3536

packages/backend/src/open_payments/payment/outgoing/service.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import { PaymentMethodHandlerService } from '../../../payment-method/handler/ser
4848
import { PaymentMethodHandlerError } from '../../../payment-method/handler/errors'
4949
import { mockRatesApi } from '../../../tests/rates'
5050
import { UnionOmit } from '../../../shared/utils'
51-
import { QuoteError } from '../../quote/errors'
51+
import { QuoteError, QuoteErrorCode } from '../../quote/errors'
5252
import { withConfigOverride } from '../../../tests/helpers'
5353
import { TelemetryService } from '../../../telemetry/service'
5454
import { getPageTests } from '../../../shared/baseModel.test'
@@ -747,10 +747,11 @@ describe('OutgoingPaymentService', (): void => {
747747
assetScale: receiverWalletAddress.asset.scale
748748
}
749749

750-
const quoteCreateResponse = QuoteError.InvalidAmount
751750
const quoteSpy = jest
752751
.spyOn(quoteService, 'create')
753-
.mockImplementationOnce(async () => quoteCreateResponse)
752+
.mockImplementationOnce(
753+
async () => new QuoteError(QuoteErrorCode.InvalidAmount)
754+
)
754755

755756
const payment = await outgoingPaymentService.create({
756757
walletAddressId,
@@ -759,7 +760,7 @@ describe('OutgoingPaymentService', (): void => {
759760
})
760761

761762
expect(isOutgoingPaymentError(payment)).toBeTruthy()
762-
expect(payment).toBe(quoteCreateResponse)
763+
expect(payment).toBe(OutgoingPaymentError.InvalidAmount)
763764
expect(quoteSpy).toHaveBeenCalledWith({
764765
walletAddressId,
765766
receiver: incomingPaymentUrl,

packages/backend/src/open_payments/payment/outgoing/service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ async function createOutgoingPayment(
276276
stopTimerQuote()
277277

278278
if (isQuoteError(quoteOrError)) {
279-
return quoteErrorToOutgoingPaymentError[quoteOrError]
279+
return quoteErrorToOutgoingPaymentError[quoteOrError.type]
280280
}
281281
quoteId = quoteOrError.id
282282
} else {
Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
import { GraphQLErrorCode } from '../../graphql/errors'
22

3-
export enum QuoteError {
3+
export class QuoteError extends Error {
4+
public type: QuoteErrorCode
5+
public details?: Record<string, unknown>
6+
7+
constructor(type: QuoteErrorCode, details?: Record<string, unknown>) {
8+
super(errorToMessage[type], details)
9+
this.type = type
10+
this.details = details
11+
}
12+
}
13+
14+
export enum QuoteErrorCode {
415
UnknownWalletAddress = 'UnknownWalletAddress',
516
InvalidAmount = 'InvalidAmount',
617
InvalidReceiver = 'InvalidReceiver',
@@ -9,35 +20,34 @@ export enum QuoteError {
920
}
1021

1122
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
12-
export const isQuoteError = (o: any): o is QuoteError =>
13-
Object.values(QuoteError).includes(o)
23+
export const isQuoteError = (o: any): o is QuoteError => o instanceof QuoteError
1424

1525
export const errorToHTTPCode: {
16-
[key in QuoteError]: number
26+
[key in QuoteErrorCode]: number
1727
} = {
18-
[QuoteError.UnknownWalletAddress]: 404,
19-
[QuoteError.InvalidAmount]: 400,
20-
[QuoteError.InvalidReceiver]: 400,
21-
[QuoteError.InactiveWalletAddress]: 400,
22-
[QuoteError.NonPositiveReceiveAmount]: 400
28+
[QuoteErrorCode.UnknownWalletAddress]: 404,
29+
[QuoteErrorCode.InvalidAmount]: 400,
30+
[QuoteErrorCode.InvalidReceiver]: 400,
31+
[QuoteErrorCode.InactiveWalletAddress]: 400,
32+
[QuoteErrorCode.NonPositiveReceiveAmount]: 400
2333
}
2434

2535
export const errorToCode: {
26-
[key in QuoteError]: GraphQLErrorCode
36+
[key in QuoteErrorCode]: GraphQLErrorCode
2737
} = {
28-
[QuoteError.UnknownWalletAddress]: GraphQLErrorCode.NotFound,
29-
[QuoteError.InvalidAmount]: GraphQLErrorCode.BadUserInput,
30-
[QuoteError.InvalidReceiver]: GraphQLErrorCode.BadUserInput,
31-
[QuoteError.InactiveWalletAddress]: GraphQLErrorCode.Inactive,
32-
[QuoteError.NonPositiveReceiveAmount]: GraphQLErrorCode.BadUserInput
38+
[QuoteErrorCode.UnknownWalletAddress]: GraphQLErrorCode.NotFound,
39+
[QuoteErrorCode.InvalidAmount]: GraphQLErrorCode.BadUserInput,
40+
[QuoteErrorCode.InvalidReceiver]: GraphQLErrorCode.BadUserInput,
41+
[QuoteErrorCode.InactiveWalletAddress]: GraphQLErrorCode.Inactive,
42+
[QuoteErrorCode.NonPositiveReceiveAmount]: GraphQLErrorCode.BadUserInput
3343
}
3444

3545
export const errorToMessage: {
36-
[key in QuoteError]: string
46+
[key in QuoteErrorCode]: string
3747
} = {
38-
[QuoteError.UnknownWalletAddress]: 'unknown wallet address',
39-
[QuoteError.InvalidAmount]: 'invalid amount',
40-
[QuoteError.InvalidReceiver]: 'invalid receiver',
41-
[QuoteError.InactiveWalletAddress]: 'inactive wallet address',
42-
[QuoteError.NonPositiveReceiveAmount]: 'non-positive receive amount'
48+
[QuoteErrorCode.UnknownWalletAddress]: 'unknown wallet address',
49+
[QuoteErrorCode.InvalidAmount]: 'invalid amount',
50+
[QuoteErrorCode.InvalidReceiver]: 'invalid receiver',
51+
[QuoteErrorCode.InactiveWalletAddress]: 'inactive wallet address',
52+
[QuoteErrorCode.NonPositiveReceiveAmount]: 'non-positive receive amount'
4353
}

packages/backend/src/open_payments/quote/routes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ async function createQuote(
9696

9797
if (isQuoteError(quoteOrErr)) {
9898
throw new OpenPaymentsServerRouteError(
99-
errorToHTTPCode[quoteOrErr],
100-
errorToMessage[quoteOrErr]
99+
errorToHTTPCode[quoteOrErr.type],
100+
errorToMessage[quoteOrErr.type],
101+
quoteOrErr.details
101102
)
102103
}
103104

0 commit comments

Comments
 (0)