Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 9 additions & 5 deletions bruno/collections/Rafiki/POS Service APIs/Initiate Payment.bru
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ post {

body:json {
{
"card": {
"walletAddress": "https://cloud-nine-wallet-backend/accounts/gfranklin",
"signature": "signature"
"signature": "signature",
"payload": "payload",
"amount": {
"value": "1000",
"assetScale": 2,
"assetCode": "USD"
},
"value": 1,
"merchantWalletAddress": "https://happy-life-bank-backend/accounts/pfry"
"senderWalletAddress": "https://cloud-nine-wallet-backend/accounts/gfranklin",
"receiverWalletAddress": "https://happy-life-bank-backend/accounts/pfry",
"timestamp": 1758105181325
}
}
6 changes: 5 additions & 1 deletion localenv/happy-life-bank/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ services:
TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
TENANT_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
GRAPHQL_URL: http://happy-life-bank-backend:3001/graphql
WEBHOOK_SIGNATURE_SECRET: webhook_secret
WEBHOOK_SIGNATURE_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
WEBHOOK_SIGNATURE_VERSION: 1
Comment on lines +67 to +68
Copy link
Contributor Author

Choose a reason for hiding this comment

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

updating the docker compose file so that the webhooks between backend <> POS service are signed and correctly validated

depends_on:
- shared-database
- cloud-nine-wallet-point-of-sale
Expand Down Expand Up @@ -144,6 +145,8 @@ services:
ILP_ADDRESS: test.happy-life-bank
ILP_CONNECTOR_URL: http://happy-life-bank-backend:4002
STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=
SIGNATURE_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964= # webhook signature
SIGNATURE_VERSION: 1
API_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
WEBHOOK_URL: http://happy-life-bank/webhooks
EXCHANGE_RATES_URL: http://happy-life-bank/rates
Expand All @@ -154,6 +157,7 @@ services:
KEY_ID: 53f2d913-e98a-40b9-b270-372d0547f23d
OPERATOR_TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
CARD_SERVICE_URL: 'http://happy-life-bank-card-service:4007'
POS_WEBHOOK_SERVICE_URL: 'http://happy-life-bank-point-of-sale:4008/webhook'
depends_on:
- cloud-nine-backend
healthcheck:
Expand Down
39 changes: 18 additions & 21 deletions packages/card-service/src/openapi/specs/card-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,38 @@ paths:
type: object
required:
- requestId
- card
- merchantWalletAddress
- signature
- payload
- amount
- timestamp
- incomingPaymentUrl
- date
# - terminalCert
- senderWalletAddress
properties:
requestId:
type: string
format: uuid
card:
signature:
type: string
payload:
type: string
amount:
type: object
required:
- walletAddress
- signature
properties:
walletAddress:
value:
type: string
format: uri
signature:
assetCode:
type: string

merchantWalletAddress:
assetScale:
type: number
senderWalletAddress:
type: string
format: uri
incomingPaymentUrl:
type: string
format: uri
date:
type: string
format: date-time
# terminalCert:
# type: string
# terminalId:
# type: string
# format: uuid
timestamp:
type: number

responses:
'201':
description: Payment request accepted
Expand Down
26 changes: 11 additions & 15 deletions packages/card-service/src/payment/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,25 @@ describe('PaymentRoutes', () => {
let appContainer: TestContainer
let routes: PaymentRoutes

const uuid = '123e4567-e89b-12d3-a456-426614174000'
const uri = 'https://example.com/wallet/123'
const dateTime = '2024-01-01T00:00:00Z'
const requestId = '123e4567-e89b-12d3-a456-426614174000'

const paymentFixture: PaymentBody = {
requestId: uuid,
card: {
walletAddress: uri,
signature: 'sig'
},
merchantWalletAddress: uri,
incomingPaymentUrl: uri,
date: dateTime,
incomingAmount: {
requestId,
senderWalletAddress: 'https://example.com/wallet/123',
signature: 'sig',
payload: 'payload',
incomingPaymentUrl: 'https://example.com/incoming-payment/123',
timestamp: new Date().getTime(),
amount: {
assetCode: 'USD',
assetScale: 0,
value: '100'
}
}

const paymentEventFixture: PaymentEventBody = {
requestId: uuid,
outgoingPaymentId: uuid,
requestId,
outgoingPaymentId: crypto.randomUUID(),
result: { code: PaymentEventResultEnum.Completed }
}

Expand Down Expand Up @@ -170,7 +166,7 @@ describe('PaymentRoutes', () => {

const deferred = new Deferred<PaymentEventBody>()
const resolveSpy = jest.spyOn(deferred, 'resolve')
paymentWaitMap.set(uuid, deferred)
paymentWaitMap.set(requestId, deferred)
await expect(routes.handlePaymentEvent(ctx)).resolves.toBeUndefined()
expect(resolveSpy).toHaveBeenCalledWith(ctx.request.body)
expect(ctx.status).toBe(202)
Expand Down
34 changes: 15 additions & 19 deletions packages/card-service/src/payment/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import { v4 } from 'uuid'
import { GET_WALLET_ADDRESS_BY_URL } from '../graphql/mutations/getWalletAddress'
import { CREATE_OUTGOING_PAYMENT_FROM_INCOMING } from '../graphql/mutations/createOutgoingPayment'

const uuid = '123e4567-e89b-12d3-a456-426614174000'
const uri = 'https://example.com/wallet/123'
const dateTime = '2024-01-01T00:00:00Z'
const requestId = '123e4567-e89b-12d3-a456-426614174000'

describe('PaymentService', () => {
let deps: IocContract<AppServices>
Expand All @@ -27,15 +25,13 @@ describe('PaymentService', () => {
let mutationSpy: jest.SpyInstance

const paymentFixture: PaymentBody = {
requestId: uuid,
card: {
walletAddress: uri,
signature: 'sig'
},
merchantWalletAddress: uri,
incomingPaymentUrl: uri,
date: dateTime,
incomingAmount: {
requestId,
signature: 'sig',
payload: 'payload',
senderWalletAddress: 'https://example.com/wallet/123',
incomingPaymentUrl: 'https://example.com/incoming-payment/123',
timestamp: new Date().getTime(),
amount: {
assetCode: 'USD',
assetScale: 2,
value: '100'
Expand Down Expand Up @@ -87,24 +83,24 @@ describe('PaymentService', () => {
describe('create', () => {
test('resolves when paymentEvent is received', async () => {
setTimeout(() => {
const d = paymentWaitMap.get(uuid)
const d = paymentWaitMap.get(requestId)
d?.resolve({
requestId: uuid,
outgoingPaymentId: uuid,
requestId: requestId,
outgoingPaymentId: requestId,
result: { code: PaymentEventResultEnum.Completed }
})
}, 10)

const result = await service.create(paymentFixture)
expect(result).toEqual({
requestId: uuid,
outgoingPaymentId: uuid,
requestId: requestId,
outgoingPaymentId: requestId,
result: { code: PaymentEventResultEnum.Completed }
})

expect(querySpy).toHaveBeenCalledWith({
query: GET_WALLET_ADDRESS_BY_URL,
variables: { url: paymentFixture.card.walletAddress }
variables: { url: paymentFixture.senderWalletAddress }
})
expect(mutationSpy).toHaveBeenCalledWith({
mutation: CREATE_OUTGOING_PAYMENT_FROM_INCOMING,
Expand All @@ -113,7 +109,7 @@ describe('PaymentService', () => {
walletAddressId: expect.any(String),
incomingPayment: paymentFixture.incomingPaymentUrl,
cardDetails: {
signature: paymentFixture.card.signature
signature: paymentFixture.signature
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/card-service/src/payment/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function handleCreatePayment(
>({
query: GET_WALLET_ADDRESS_BY_URL,
variables: {
url: payment.card.walletAddress
url: payment.senderWalletAddress
}
})

Expand All @@ -79,7 +79,7 @@ async function handleCreatePayment(
walletAddressId,
incomingPayment: payment.incomingPaymentUrl,
cardDetails: {
signature: payment.card.signature
signature: payment.signature
}
}
}
Expand Down
20 changes: 11 additions & 9 deletions packages/card-service/src/payment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ export interface CardDetails {
signature: string
}

interface Amount {
value: string
assetScale: number
assetCode: string
}

export interface PaymentBody {
requestId: string
card: CardDetails
merchantWalletAddress: string
signature: string
payload: string
amount: Amount
senderWalletAddress: string
incomingPaymentUrl: string
date: string
// terminalCert: string
incomingAmount: {
assetCode: string
assetScale: number
value: string
}
timestamp: number
}

export type PaymentContext = Omit<AppContext, 'request'> & {
Expand Down
16 changes: 7 additions & 9 deletions packages/point-of-sale/src/card-service-client/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
CardServiceClient,
PaymentOptions,
SendPaymentArgs,
PaymentResponse,
Result,
createCardServiceClient
Expand Down Expand Up @@ -33,15 +33,13 @@ describe('CardServiceClient', () => {
result: result ?? Result.APPROVED
})

const options: PaymentOptions = {
const options: SendPaymentArgs = {
incomingPaymentUrl: 'incomingPaymentUrl',
merchantWalletAddress: '',
date: new Date(),
card: {
walletAddress: faker.internet.url(),
signature: ''
},
incomingAmount: {
senderWalletAddress: faker.internet.url(),
timestamp: new Date().getTime(),
signature: '',
payload: '',
amount: {
assetCode: 'USD',
assetScale: 2,
value: '100'
Expand Down
37 changes: 18 additions & 19 deletions packages/point-of-sale/src/card-service-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,32 @@ import { CardServiceClientError } from './errors'
import { v4 as uuid } from 'uuid'
import { BaseService } from '../shared/baseService'

export interface Card {
signature: string
walletAddress: string
interface Amount {
value: string
assetScale: number
assetCode: string
}

export interface PaymentOptions {
merchantWalletAddress: string
export interface SendPaymentArgs {
signature: string
payload: string
amount: Amount
incomingPaymentUrl: string
date: string
card: Card
incomingAmount: {
assetCode: string
assetScale: number
value: string
}
senderWalletAddress: string
timestamp: number
}

interface CardServicePaymentRequest extends SendPaymentArgs {
requestId: string
}

export interface CardServiceClient {
sendPayment(cardServiceUrl: string, options: PaymentOptions): Promise<Result>
sendPayment(cardServiceUrl: string, options: SendPaymentArgs): Promise<Result>
}

interface ServiceDependencies extends BaseService {
axios: AxiosInstance
}
interface PaymentOptionsWithReqId extends PaymentOptions {
requestId: string
}

export enum Result {
APPROVED = 'approved',
Expand Down Expand Up @@ -74,16 +73,16 @@ export async function createCardServiceClient({
async function sendPayment(
deps: ServiceDependencies,
cardServiceUrl: string,
options: PaymentOptions
args: SendPaymentArgs
): Promise<Result> {
try {
const config: AxiosRequestConfig = {
headers: {
'Content-Type': 'application/json'
}
}
const requestBody: PaymentOptionsWithReqId = {
...options,
const requestBody: CardServicePaymentRequest = {
...args,
requestId: uuid()
}
const response = await deps.axios.post<PaymentResponse>(
Expand Down
13 changes: 13 additions & 0 deletions packages/point-of-sale/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ import axios from 'axios'
import { createCardServiceClient } from './card-service-client/client'
import { createWebhookHandlerRoutes } from './webhook-handlers/routes'

/* eslint-disable no-var */
declare global {
interface BigInt {
Comment on lines +28 to +30
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So BigInt is properly coerced before we pass it into the GraphQL requests

toJSON(): string
}
}
/* eslint-enable no-var */

// eslint-disable-next-line no-extend-native
BigInt.prototype.toJSON = function (this: bigint) {
return this.toString()
}

export function initIocContainer(
config: typeof Config
): IocContract<AppServices> {
Expand Down
Loading
Loading