Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: payment method handler service (quoting) #1974

Merged
merged 9 commits into from
Oct 6, 2023

Conversation

mkurapov
Copy link
Contributor

@mkurapov mkurapov commented Oct 3, 2023

Changes proposed in this pull request

  • Adds paymentManagerHandlerService which will know how to call different payment method services for each payment method (for now, just ilpPaymentService)
  • Adds ilpPaymentService that has a getQuote method which handles ILP payments only (essentially copied over from QuoteService.create
  • Adds additionalFields property to quotes to store any payment method-specific quote information

Note: these new services are only added in this PR, not used (to break up into smaller PRs). Next up:

  • Using paymentMethodHandlerService.getQuote in QuoteService.create to abstract away ILP quoting

Context

Progress towards #1967:

In order to be able to support multiple payment methods, we need a service that knows how to do quoting and transfers without having the callers of the service (QuoteService, OutgoingPaymentService) know exactly about the details of how quoting and paying are done for different payment methods.

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Documentation added
  • Make sure that all checks pass
  • Postman collection updated

@netlify
Copy link

netlify bot commented Oct 3, 2023

Deploy Preview for brilliant-pasca-3e80ec canceled.

Name Link
🔨 Latest commit bdfb97b
🔍 Latest deploy log https://app.netlify.com/sites/brilliant-pasca-3e80ec/deploys/651de439510d900008f8b5bd

@mkurapov mkurapov changed the title Mk/1967/payment manager service feat: payment manager service Oct 3, 2023
@github-actions github-actions bot added type: tests Testing related pkg: backend Changes in the backend package. type: source Changes business logic labels Oct 3, 2023
@mkurapov mkurapov force-pushed the mk/1967/payment-manager-service branch from 3e3ac24 to b6d3ea7 Compare October 3, 2023 19:42
@mkurapov mkurapov changed the title feat: payment manager service feat: payment method handler service Oct 3, 2023
@mkurapov mkurapov changed the title feat: payment method handler service feat: payment method handler service (for quoting) Oct 3, 2023
Comment on lines +250 to +256
test.each`
incomingAssetCode | incomingAmountValue | debitAssetCode | expectedDebitAmount | exchangeRate | slippage | description
${'EUR'} | ${100n} | ${'USD'} | ${101n} | ${1.0} | ${0} | ${'same currency, no slippage'}
${'USD'} | ${100n} | ${'USD'} | ${102n} | ${1.0} | ${0.01} | ${'same currency, some slippage'}
${'EUR'} | ${100n} | ${'USD'} | ${113n} | ${0.9} | ${0.01} | ${'cross currency, exchange rate < 1'}
${'EUR'} | ${100n} | ${'USD'} | ${51n} | ${2.0} | ${0.01} | ${'cross currency, exchange rate > 1'}
`(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good demonstration of the interledger Pay library behaviour. This may look odd, but some of these are programmed into the library, for example, the off-by-one "issue", where an extra 1 unit is added to the debitAmount to account for possible packet loss.

Some behavior is weird for example, slippage being taken into account for a payment within the same asset.
We have taken note of this behaviour, and will continue investigating.

}
}

async function getQuote(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically copy of the ILP-related quoteService.create method

Comment on lines +113 to +118
additionalFields: {
lowEstimatedExchangeRate: ilpQuote.lowEstimatedExchangeRate,
highEstimatedExchangeRate: ilpQuote.highEstimatedExchangeRate,
minExchangeRate: ilpQuote.minExchangeRate,
maxPacketAmount: ilpQuote.maxPacketAmount
}
Copy link
Contributor Author

@mkurapov mkurapov Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ILP specific stuff is now in the additionalFields (will be the same case for other payment methods)

exchangeRate?: number
} & ({ debitAmountValue: bigint } | { receiveAmountValue: bigint })

export function mockQuote(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used in this PR, but will make sense in the next

Comment on lines +437 to +454
container.singleton('ilpPaymentService', async (deps) => {
return createIlpPaymentService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
config: await deps.use('config'),
makeIlpPlugin: await deps.use('makeIlpPlugin'),
ratesService: await deps.use('ratesService')
})
})

container.singleton('paymentMethodHandlerService', async (deps) => {
return createPaymentMethodHandlerService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
ilpPaymentService: await deps.use('ilpPaymentService')
})
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used yet, but will be in the next PR

@mkurapov mkurapov marked this pull request as ready for review October 3, 2023 20:04
Comment on lines +7 to +24
export interface StartQuoteOptions {
paymentPointer: PaymentPointer
debitAmount?: Amount
receiveAmount?: Amount
receiver: Receiver
}

export interface PaymentQuote {
paymentPointer: PaymentPointer
receiver: Receiver
debitAmount: Amount
receiveAmount: Amount
additionalFields: Record<string, unknown>
}

export interface PaymentMethodService {
getQuote(quoteOptions: StartQuoteOptions): Promise<PaymentQuote>
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interfaces that will need to be satisfied for all payment method handlers

@mkurapov mkurapov changed the title feat: payment method handler service (for quoting) feat: payment method handler service (quoting) Oct 3, 2023
Comment on lines +447 to +453
container.singleton('paymentMethodHandlerService', async (deps) => {
return createPaymentMethodHandlerService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
ilpPaymentService: await deps.use('ilpPaymentService')
})
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is a minor part of integrating different payment methods into Rafiki, but I'm currently a bit confused. Wouldn't it be more appropriate to have this as factory that is instantiated at runtime based on the payment method?

What I mean by that:

export async function createPaymentMethodHandlerService({
    logger,
    knex,
-   ilpPaymentService
+   paymentMethod // ('ilp', '...');
}: ServiceDependencies): Promise<PaymentMethodHandlerService> {
    const log = logger.child({
        service: "PaymentMethodHandlerService",
    });
    const deps: ServiceDependencies = {
        logger: log,
        knex,
        ilpPaymentService,
    };
   
+   const paymentMethod = new Payment(paymentMethod)

    return {
-        getQuote: (method, quoteOptions) =>
-           getPaymentMethodService(deps, method).getQuote(quoteOptions),
+        getQuote: (quoteOptions) =>
+           paymentMethod.getQuote(quoteOptions),
    };
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To try and build on this idea, maybe it could take in an object whose keys are the names of supported payment methods, and the values are its services. So getQuote would then look like:

/*
  PaymentMethods: {
    ilp: IlpPaymentService
    ...other future payment method services
  }

  paymentMethods: PaymentMethods
*/

getQuote: (method, quoteOptions) => paymentMethods[method].getQuote(quoteOptions)

Copy link
Contributor Author

@mkurapov mkurapov Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raducristianpopa the PaymentMethodHandlerService is our "factory" in this case already, but instead of a new instance of a class we're just returning the service/singleton for the specific payment method that extends from PaymentMethodService. I could rename PaymentMethodHandlerService to PaymentMethodFactory if that seems better?

@njlie yeah I think I'll change it to the suggestion since the types make it sort of "safe" and prevent passing in an invalid payment method 👍

Copy link
Contributor

@njlie njlie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mkurapov mkurapov merged commit 6561c60 into main Oct 6, 2023
@mkurapov mkurapov deleted the mk/1967/payment-manager-service branch October 6, 2023 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: backend Changes in the backend package. type: source Changes business logic type: tests Testing related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants