Skip to content
6 changes: 6 additions & 0 deletions localenv/happy-life-bank/seed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ accounts:
initialBalance: 2000
path: accounts/planex
postmanEnvVar: planexPaymentPointer
- name: 'Alice Smith'
path: accounts/asmith
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe alias is better than path but just went with what was there originally

id: f47ac10b-58cc-4372-a567-0e02b2c3d479
initialBalance: 500
postmanEnvVar: asmithPaymentPointer
skipPaymentPointerCreation: true
fees:
- fixed: 1
percentage: 0.02
34 changes: 29 additions & 5 deletions localenv/mock-account-servicing-entity/app/lib/accounts.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface Account {
id: string
name: string
path: string
paymentPointerID: string
paymentPointer: string
debitsPending: bigint
Expand All @@ -9,6 +10,7 @@ export interface Account {
creditsPosted: bigint
assetCode: string
assetScale: number
assetId: string
}

export interface AccountsServer {
Expand All @@ -20,13 +22,19 @@ export interface AccountsServer {
): Promise<void>
create(
id: string,
path: string,
name: string,
assetCode: string,
assetScale: number
assetScale: number,
assetId: string
): Promise<void>
listAll(): Promise<Account[]>
get(id: string): Promise<Account | undefined>
getByPaymentPointer(paymentPointer: string): Promise<Account | undefined>
getByPaymentPointerId(paymentPointerId: string): Promise<Account | undefined>
getByPath(path: string): Promise<Account | undefined>
getByPaymentPointerUrl(
paymentPointerUrl: string
): Promise<Account | undefined>
voidPendingDebit(id: string, amount: bigint): Promise<void>
voidPendingCredit(id: string, amount: bigint): Promise<void>
pendingDebit(id: string, amount: bigint): Promise<void>
Expand Down Expand Up @@ -63,24 +71,28 @@ export class AccountProvider implements AccountsServer {

async create(
id: string,
path: string,
name: string,
assetCode: string,
assetScale: number
assetScale: number,
assetId: string
): Promise<void> {
if (this.accounts.has(id)) {
throw new Error('account already exists')
}
this.accounts.set(id, {
id,
name,
path,
paymentPointer: '',
paymentPointerID: '',
creditsPending: BigInt(0),
creditsPosted: BigInt(0),
debitsPending: BigInt(0),
debitsPosted: BigInt(0),
assetCode,
assetScale
assetScale,
assetId
})
}

Expand All @@ -92,7 +104,7 @@ export class AccountProvider implements AccountsServer {
return this.accounts.get(id)
}

async getByPaymentPointer(
async getByPaymentPointerId(
paymentPointerId: string
): Promise<Account | undefined> {
for (const acc of this.accounts.values()) {
Expand All @@ -102,6 +114,18 @@ export class AccountProvider implements AccountsServer {
}
}

async getByPaymentPointerUrl(
paymentPointerUrl: string
): Promise<Account | undefined> {
return (await this.listAll()).find(
(acc) => acc.paymentPointer === paymentPointerUrl
)
}

async getByPath(path: string): Promise<Account | undefined> {
return (await this.listAll()).find((acc) => acc.path === path)
}

async credit(
id: string,
amount: bigint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface Account {
initialBalance: bigint
path: string
postmanEnvVar: string
skipPaymentPointerCreation?: boolean
}

export interface SeedInstance {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ export async function addPeerLiquidity(
}

export async function createPaymentPointer(
backendUrl: string,
accountName: string,
accountUrl: string,
assetId: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ export async function setupFromSeed(config: Config): Promise<void> {
config.seed.accounts.map(async (account: Account) => {
await mockAccounts.create(
account.id,
account.path,
account.name,
asset.code,
asset.scale
asset.scale,
asset.id
)
if (account.initialBalance) {
await mockAccounts.credit(
Expand All @@ -66,8 +68,12 @@ export async function setupFromSeed(config: Config): Promise<void> {
false
)
}

if (account.skipPaymentPointerCreation) {
return
}

const paymentPointer = await createPaymentPointer(
config.seed.self.graphqlUrl,
account.name,
`https://${CONFIG.seed.self.hostname}/${account.path}`,
asset.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export async function getAccountTransactions(
accountId: string
): Promise<Array<IncomingPayment | OutgoingPayment>> {
const account = await mockAccounts.get(accountId)

if (!account?.paymentPointerID) {
return []
}

const { incomingPayments, outgoingPayments } =
await getPaymentPointerPayments(account.paymentPointerID)
const transactions = incomingPayments.edges.map(({ node }) => {
Expand Down
41 changes: 37 additions & 4 deletions localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import type { Amount } from './transactions.server'
import { mockAccounts } from './accounts.server'
import { apolloClient } from './apolloClient'
import { v4 as uuid } from 'uuid'
import { createPaymentPointer } from './requesters'
import { CONFIG } from './parse_config.server'

export enum EventType {
IncomingPaymentCreated = 'incoming_payment.created',
IncomingPaymentCompleted = 'incoming_payment.completed',
IncomingPaymentExpired = 'incoming_payment.expired',
OutgoingPaymentCreated = 'outgoing_payment.created',
OutgoingPaymentCompleted = 'outgoing_payment.completed',
OutgoingPaymentFailed = 'outgoing_payment.failed'
OutgoingPaymentFailed = 'outgoing_payment.failed',
PaymentPointerNotFound = 'payment_pointer.not_found'
}

export interface WebHook {
Expand Down Expand Up @@ -43,7 +46,7 @@ export async function handleOutgoingPaymentCompletedFailed(wh: WebHook) {
}
const payment = wh.data['payment']
const pp = payment['paymentPointerId'] as string
const acc = await mockAccounts.getByPaymentPointer(pp)
const acc = await mockAccounts.getByPaymentPointerId(pp)

if (!acc) {
throw new Error('No account found for payment pointer')
Expand Down Expand Up @@ -71,7 +74,7 @@ export async function handleOutgoingPaymentCreated(wh: WebHook) {

const payment = wh.data['payment']
const pp = payment['paymentPointerId'] as string
const acc = await mockAccounts.getByPaymentPointer(pp)
const acc = await mockAccounts.getByPaymentPointerId(pp)

if (!acc) {
throw new Error('No account found for payment pointer')
Expand Down Expand Up @@ -122,7 +125,7 @@ export async function handleIncomingPaymentCompletedExpired(wh: WebHook) {

const payment = wh.data['incomingPayment']
const pp = payment['paymentPointerId'] as string
const acc = await mockAccounts.getByPaymentPointer(pp)
const acc = await mockAccounts.getByPaymentPointerId(pp)

if (!acc) {
throw new Error('No account found for payment pointer')
Expand Down Expand Up @@ -161,3 +164,33 @@ export async function handleIncomingPaymentCompletedExpired(wh: WebHook) {

return
}

export async function handlePaymentPointerNotFound(wh: WebHook) {
const paymentPointerUrl = wh.data['paymentPointerUrl'] as string | undefined

if (!paymentPointerUrl) {
throw new Error('No paymentPointerUrl found')
}

const accountPath = paymentPointerUrl.split(
`https://${CONFIG.seed.self.hostname}/`
)[1]

const account = await mockAccounts.getByPath(accountPath)

if (!account) {
throw new Error('No account found for payment pointer')
}

const paymentPointer = await createPaymentPointer(
account.name,
paymentPointerUrl,
account.assetId
)

await mockAccounts.setPaymentPointer(
account.id,
paymentPointer.id,
paymentPointer.url
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ActionArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import type { WebHook } from '~/lib/webhooks.server'
import { handlePaymentPointerNotFound, WebHook } from '~/lib/webhooks.server'
import {
handleOutgoingPaymentCreated,
handleOutgoingPaymentCompletedFailed,
Expand Down Expand Up @@ -31,6 +31,9 @@ export async function action({ request }: ActionArgs) {
case EventType.IncomingPaymentExpired:
await handleIncomingPaymentCompletedExpired(wh)
break
case EventType.PaymentPointerNotFound:
await handlePaymentPointerNotFound(wh)
break
default:
console.log(`unknown event type: ${wh.type}`)
return json(undefined, { status: 400 })
Expand Down
10 changes: 8 additions & 2 deletions packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,18 @@ export const Config = {
'GRAPHQL_IDEMPOTENCY_KEY_TTL_MS',
86400000
),

paymentPointerLookupTimeoutMs: envInt(
'PAYMENT_POINTER_LOOKUP_TIMEOUT_MS',
1500
),
paymentPointerPollingFrequencyMs: envInt(
'PAYMENT_POINTER_POLLING_FREQUENCY_MS',
100
),
paymentPointerDeactivationPaymentGracePeriodMs: envInt(
'PAYMENT_POINTER_DEACTIVATION_PAYMENT_GRACE_PERIOD_MS',
86400000
),

incomingPaymentExpiryMaxMs: envInt(
'INCOMING_PAYMENT_EXPIRY_MAX_MS',
2592000000
Expand Down
18 changes: 10 additions & 8 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,21 @@ export function initIocContainer(
knex: await deps.use('knex')
})
})
container.singleton('webhookService', async (deps) => {
return createWebhookService({
config: await deps.use('config'),
knex: await deps.use('knex'),
logger: await deps.use('logger')
})
})
container.singleton('paymentPointerService', async (deps) => {
const logger = await deps.use('logger')
return await createPaymentPointerService({
config: await deps.use('config'),
knex: await deps.use('knex'),
logger: logger,
accountingService: await deps.use('accountingService'),
config: await deps.use('config')
webhookService: await deps.use('webhookService')
})
})
container.singleton('spspRoutes', async (deps) => {
Expand All @@ -232,13 +240,7 @@ export function initIocContainer(
streamServer: streamServer
})
})
container.singleton('webhookService', async (deps) => {
return createWebhookService({
config: await deps.use('config'),
knex: await deps.use('knex'),
logger: await deps.use('logger')
})
})

container.singleton('incomingPaymentService', async (deps) => {
return await createIncomingPaymentService({
logger: await deps.use('logger'),
Expand Down
9 changes: 7 additions & 2 deletions packages/backend/src/open_payments/payment_pointer/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export class PaymentPointer
}

export enum PaymentPointerEventType {
PaymentPointerWebMonetization = 'payment_pointer.web_monetization'
PaymentPointerWebMonetization = 'payment_pointer.web_monetization',
PaymentPointerNotFound = 'payment_pointer.not_found'
}

export type PaymentPointerData = {
Expand All @@ -117,9 +118,13 @@ export type PaymentPointerData = {
}
}

export type PaymentPointerRequestedData = {
paymentPointerUrl: string
}

export class PaymentPointerEvent extends WebhookEvent {
public type!: PaymentPointerEventType
public data!: PaymentPointerData
public data!: PaymentPointerData | PaymentPointerRequestedData
}

export interface GetOptions {
Expand Down
Loading