Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3dc1ee6
feat: add ILP_ADDRESS tenant setting, generate default one using tena…
mkurapov Jun 10, 2025
df2968b
chore: decouple incomingPayment from streamCredentialGeneration
mkurapov Jun 10, 2025
0ab8928
chore: generate StreamServer every time we get getStreamCredentials
mkurapov Jun 10, 2025
5de703e
feat(backend): allowing passing in ilpAddress when generating stream …
mkurapov Jun 10, 2025
e22133b
feat: adding paymentMethodProviderService
mkurapov Jun 10, 2025
68deaee
feat: use paymentMethodProviderService in receiverService
mkurapov Jun 10, 2025
588dad0
feat: use paymentMethodProviderService in incomingPayment service & m…
mkurapov Jun 10, 2025
d3742c7
feat: use updated receiver structure to resolve ilp payment destinati…
mkurapov Jun 10, 2025
38d0234
feat: use updated receiver structure in outgoingPaymentService
mkurapov Jun 10, 2025
772a283
chore: add paymentMethodProviderService to app init
mkurapov Jun 10, 2025
fa8b94a
chore: update test files to use paymentMethodProviderService
mkurapov Jun 10, 2025
fb6ca37
Merge branch '2893/multi-tenancy-v1' into max/raf-1069
mkurapov Jun 10, 2025
0f2c1b0
chore: lint
mkurapov Jun 10, 2025
b3a94f5
test: use paymentMethodProviderService.getPaymentMethods during outgo…
mkurapov Jun 10, 2025
12df355
chore(localenv): add ilpAddress for non-operator tenant for happy-life
mkurapov Jun 10, 2025
1505e93
feat: update connector, peer service to determine & parse destination…
mkurapov Jun 11, 2025
9ae2239
fix: use correct redirect for tenanted mock-idp
njlie Jun 11, 2025
c85f7aa
test(backend): adding test for longest prefix match for peer
mkurapov Jun 11, 2025
81a4d5f
test(backend): add tests for stream-address middleware
mkurapov Jun 12, 2025
d39f7cb
feat(backend): only set streamDestination & streamServer defined if v…
mkurapov Jun 13, 2025
9c8cfc5
chore(backend): remove unused part of account middleware
mkurapov Jun 13, 2025
619c575
chore(backend): add types to middleware
mkurapov Jun 13, 2025
a5477f3
test(backend): update stream-address middleware test
mkurapov Jun 13, 2025
b778c97
test(backend): test fixes
mkurapov Jun 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ meta {
}

post {
url: {{senderOpenPaymentsAuthHost}}/continue/{{continueId}}
url: {{senderTenantOpenPaymentsAuthHost}}/continue/{{continueId}}
body: json
auth: none
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ meta {
}

post {
url: {{senderOpenPaymentsHost}}/outgoing-payments
url: {{senderTenantOpenPaymentsHost}}/outgoing-payments
body: json
auth: none
}
Expand All @@ -16,8 +16,8 @@ headers {

body:json {
{
"walletAddress": "{{senderWalletAddress}}",
"quoteId": "{{senderWalletAddress}}/quotes/{{quoteId}}",
"walletAddress": "{{senderTenantWalletAddress}}",
"quoteId": "{{senderTenantWalletAddress}}/quotes/{{quoteId}}",
"metadata": {
"description": "Free Money!"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ meta {
}

get {
url: {{senderOpenPaymentsHost}}/outgoing-payments/{{outgoingPaymentId}}
url: {{senderTenantOpenPaymentsHost}}/outgoing-payments/{{outgoingPaymentId}}
body: none
auth: none
}
Expand Down
2 changes: 1 addition & 1 deletion localenv/cloud-ten-wallet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ services:
GRAPHQL_URL: http://cloud-nine-wallet-backend:3001/graphql
SIGNATURE_VERSION: 1
SIGNATURE_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
IDP_SECRET: 2pEcn2kkCclbOHQiGNEwhJ0rucATZhrA807HTm2rNXE=
IDP_SECRET: ue3ixgIiWLIlWOd4w5KO78scYpFH+vHuCJ33lnjgzEg=
DISPLAY_NAME: Cloud Ten Wallet
DISPLAY_ICON: wallet-icon.svg
OPERATOR_TENANT_ID: 438fa74a-fa7d-4317-9ced-dde32ece1787
Expand Down
2 changes: 1 addition & 1 deletion localenv/happy-life-bank/seed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ peers:
outgoing: test-USD-happy-life-bank-cloud-nine-wallet
- initialLiquidity: '1000000000000'
peerUrl: http://cloud-nine-wallet-backend:3002
peerIlpAddress: test.cloud-nine-wallet
peerIlpAddress: test.cloud-nine-wallet.bc293b79-8609-47bd-b914-6438b470aff8
liquidityThreshold: 1000000
tokens:
incoming:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ interface GrantAmount {
}

export function loader() {
return json({ defaultIdpSecret: CONFIG.idpSecret })
return json({
defaultIdpSecret: CONFIG.idpSecret,
isTenant: process.env.IS_TENANT === 'true'
})
}

function ConsentScreenBody({
Expand Down Expand Up @@ -207,13 +210,14 @@ type ConsentScreenProps = {

// In production, ensure that secrets are handled securely and are not exposed to the client-side code.
export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) {
const { defaultIdpSecret, isTenant } = useLoaderData<typeof loader>()
const [ctx, setCtx] = useState({
ready: false,
thirdPartyName: '',
thirdPartyUri: '',
interactId: 'demo-interact-id',
nonce: 'demo-interact-nonce',
returnUrl: 'http://localhost:3030/mock-idp/consent?',
returnUrl: `http://localhost:${isTenant ? 5030 : 3030}/mock-idp/consent?`,
//TODO returnUrl: 'http://localhost:3030/mock-idp/consent?interactid=demo-interact-id&nonce=demo-interact-nonce',
accesses: null,
outgoingPaymentAccess: null,
Expand All @@ -225,7 +229,6 @@ export default function ConsentScreen({ idpSecretParam }: ConsentScreenProps) {
const queryParams = new URLSearchParams(location.search)
const instanceConfig: InstanceConfig = useOutletContext()

const { defaultIdpSecret } = useLoaderData<typeof loader>()
const idpSecret = idpSecretParam ? idpSecretParam : defaultIdpSecret

useEffect(() => {
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ import {
import { TenantService } from './tenants/service'
import { AuthServiceClient } from './auth-service-client/client'
import { TenantSettingService } from './tenants/settings/service'
import { StreamCredentialsService } from './payment-method/ilp/stream-credentials/service'
import { PaymentMethodProviderService } from './payment-method/provider/service'

export interface AppContextData {
logger: Logger
Expand Down Expand Up @@ -278,6 +280,8 @@ export interface AppServices {
tenantService: Promise<TenantService>
authServiceClient: AuthServiceClient
tenantSettingService: Promise<TenantSettingService>
streamCredentialsService: Promise<StreamCredentialsService>
paymentMethodProviderService: Promise<PaymentMethodProviderService>
}

export type AppContainer = IocContract<AppServices>
Expand Down
30 changes: 22 additions & 8 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { createInMemoryDataStore } from './middleware/cache/data-stores/in-memor
import { createTenantService } from './tenants/service'
import { AuthServiceClient } from './auth-service-client/client'
import { createTenantSettingService } from './tenants/settings/service'
import { createPaymentMethodProviderService } from './payment-method/provider/service'

BigInt.prototype.toJSON = function () {
return this.toString()
Expand Down Expand Up @@ -249,6 +250,16 @@ export function initIocContainer(
return createTenantSettingService({ logger, knex })
})

container.singleton('paymentMethodProviderService', async (deps) => {
return createPaymentMethodProviderService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
config: await deps.use('config'),
streamCredentialsService: await deps.use('streamCredentialsService'),
tenantSettingsService: await deps.use('tenantSettingService')
})
})

container.singleton('ratesService', async (deps) => {
const config = await deps.use('config')
return createRatesService({
Expand Down Expand Up @@ -460,7 +471,9 @@ export function initIocContainer(
config: await deps.use('config'),
logger: await deps.use('logger'),
incomingPaymentService: await deps.use('incomingPaymentService'),
streamCredentialsService: await deps.use('streamCredentialsService')
paymentMethodProviderService: await deps.use(
'paymentMethodProviderService'
)
})
})
container.singleton('walletAddressRoutes', async (deps) => {
Expand All @@ -478,24 +491,24 @@ export function initIocContainer(
})
})
container.singleton('streamCredentialsService', async (deps) => {
const config = await deps.use('config')
return await createStreamCredentialsService({
logger: await deps.use('logger'),
openPaymentsUrl: config.openPaymentsUrl,
streamServer: await deps.use('streamServer')
config: await deps.use('config')
})
})
container.singleton('receiverService', async (deps) => {
return await createReceiverService({
logger: await deps.use('logger'),
config: await deps.use('config'),
streamCredentialsService: await deps.use('streamCredentialsService'),
incomingPaymentService: await deps.use('incomingPaymentService'),
walletAddressService: await deps.use('walletAddressService'),
remoteIncomingPaymentService: await deps.use(
'remoteIncomingPaymentService'
),
telemetry: await deps.use('telemetry')
telemetry: await deps.use('telemetry'),
paymentMethodProviderService: await deps.use(
'paymentMethodProviderService'
)
})
})

Expand All @@ -510,15 +523,16 @@ export function initIocContainer(
const config = await deps.use('config')
return await createConnectorService({
logger: await deps.use('logger'),
config: await deps.use('config'),
redis: await deps.use('redis'),
accountingService: await deps.use('accountingService'),
walletAddressService: await deps.use('walletAddressService'),
incomingPaymentService: await deps.use('incomingPaymentService'),
peerService: await deps.use('peerService'),
ratesService: await deps.use('ratesService'),
streamServer: await deps.use('streamServer'),
ilpAddress: config.ilpAddress,
telemetry: await deps.use('telemetry')
telemetry: await deps.use('telemetry'),
tenantSettingService: await deps.use('tenantSettingService')
})
})

Expand Down
77 changes: 9 additions & 68 deletions packages/backend/src/open_payments/payment/incoming/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ import { AppServices } from '../../../app'
import { createIncomingPayment } from '../../../tests/incomingPayment'
import { createWalletAddress } from '../../../tests/walletAddress'
import { truncateTables } from '../../../tests/tableManager'
import { IlpStreamCredentials } from '../../../payment-method/ilp/stream-credentials/service'
import { serializeAmount } from '../../amount'
import { IlpAddress } from 'ilp-packet'
import {
IncomingPayment,
IncomingPaymentEvent,
IncomingPaymentEventType,
IncomingPaymentState,
IncomingPaymentEventError
} from './model'
import { WalletAddress } from '../../wallet_address/model'
import { OpenPaymentsPaymentMethod } from '../../../payment-method/provider/service'

describe('Models', (): void => {
let deps: IocContract<AppServices>
Expand Down Expand Up @@ -80,16 +79,19 @@ describe('Models', (): void => {

describe('toOpenPaymentsTypeWithMethods', () => {
test('returns incoming payment with payment methods', async () => {
const streamCredentials: IlpStreamCredentials = {
ilpAddress: 'test.ilp' as IlpAddress,
sharedSecret: Buffer.from('')
}
const paymentMethods: OpenPaymentsPaymentMethod[] = [
{
type: 'ilp',
ilpAddress: 'test.ilp' as IlpAddress,
sharedSecret: ''
}
]

expect(
incomingPayment.toOpenPaymentsTypeWithMethods(
config.openPaymentsUrl,
walletAddress,
streamCredentials
paymentMethods
)
).toEqual({
id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`,
Expand All @@ -112,67 +114,6 @@ describe('Models', (): void => {
]
})
})

test('returns incoming payment with empty methods when stream credentials are undefined', async () => {
expect(
incomingPayment.toOpenPaymentsTypeWithMethods(
config.openPaymentsUrl,
walletAddress
)
).toEqual({
id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`,
walletAddress: walletAddress.address,
completed: incomingPayment.completed,
receivedAmount: serializeAmount(incomingPayment.receivedAmount),
incomingAmount: incomingPayment.incomingAmount
? serializeAmount(incomingPayment.incomingAmount)
: undefined,
expiresAt: incomingPayment.expiresAt.toISOString(),
metadata: incomingPayment.metadata ?? undefined,
updatedAt: incomingPayment.updatedAt.toISOString(),
createdAt: incomingPayment.createdAt.toISOString(),
methods: []
})
})

test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])(
'returns incoming payment with existing methods if payment state is %s',
Comment on lines -116 to -139
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These conditions were not being checked in the model anyway

async (paymentState): Promise<void> => {
incomingPayment.state = paymentState

const streamCredentials: IlpStreamCredentials = {
ilpAddress: 'test.ilp' as IlpAddress,
sharedSecret: Buffer.from('')
}

expect(
incomingPayment.toOpenPaymentsTypeWithMethods(
config.openPaymentsUrl,
walletAddress,
streamCredentials
)
).toMatchObject({
id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`,
walletAddress: walletAddress.address,
completed: incomingPayment.completed,
receivedAmount: serializeAmount(incomingPayment.receivedAmount),
incomingAmount: incomingPayment.incomingAmount
? serializeAmount(incomingPayment.incomingAmount)
: undefined,
expiresAt: incomingPayment.expiresAt.toISOString(),
metadata: incomingPayment.metadata ?? undefined,
updatedAt: incomingPayment.updatedAt.toISOString(),
createdAt: incomingPayment.createdAt.toISOString(),
methods: [
expect.objectContaining({
type: 'ilp',
ilpAddress: streamCredentials.ilpAddress,
sharedSecret: expect.any(String)
})
]
})
}
)
})
})

Expand Down
15 changes: 3 additions & 12 deletions packages/backend/src/open_payments/payment/incoming/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Model, QueryContext } from 'objection'

import { Amount, AmountJSON, serializeAmount } from '../../amount'
import { IlpStreamCredentials } from '../../../payment-method/ilp/stream-credentials/service'
import {
WalletAddress,
WalletAddressSubresource
Expand All @@ -14,7 +13,7 @@ import {
IncomingPayment as OpenPaymentsIncomingPayment,
IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethod
} from '@interledger/open-payments'
import base64url from 'base64url'
import { OpenPaymentsPaymentMethod } from '../../../payment-method/provider/service'

export enum IncomingPaymentEventType {
IncomingPaymentCreated = 'incoming_payment.created',
Expand Down Expand Up @@ -242,19 +241,11 @@ export class IncomingPayment
public toOpenPaymentsTypeWithMethods(
resourceServerUrl: string,
walletAddress: WalletAddress,
ilpStreamCredentials?: IlpStreamCredentials
paymentMethods: OpenPaymentsPaymentMethod[]
): OpenPaymentsIncomingPaymentWithPaymentMethod {
return {
...this.toOpenPaymentsType(resourceServerUrl, walletAddress),
methods: !ilpStreamCredentials
? []
: [
{
type: 'ilp',
ilpAddress: ilpStreamCredentials.ilpAddress,
sharedSecret: base64url(ilpStreamCredentials.sharedSecret)
}
]
methods: paymentMethods
}
}

Expand Down
Loading
Loading