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
1 change: 1 addition & 0 deletions packages/card-service/jest.env.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Jest environment configuration for card-service
process.env.NODE_ENV = 'test'
process.env.LOG_LEVEL = process.env.LOG_LEVEL || 'silent'
process.env.CARD_PAYMENT_TIMEOUT_MS = '50'
12 changes: 7 additions & 5 deletions packages/card-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
},
"dependencies": {
"@adonisjs/fold": "^8.2.0",
"@interledger/openapi": "2.0.2",
"@koa/cors": "^5.0.0",
"ioredis": "^5.3.2",
"@koa/router": "^12.0.2",
"koa-bodyparser": "^4.4.1",
"koa": "^2.15.4",
"knex": "^3.1.0",
"koa": "^2.15.4",
"koa-bodyparser": "^4.4.1",
"objection": "^3.1.5",
"pg": "^8.11.3",
"objection-db-errors": "^1.1.2",
"pg": "^8.11.3",
"pino": "^8.19.0",
"uuid": "^9.0.1"
},
Expand All @@ -35,8 +36,9 @@
"@types/koa__router": "^12.0.4",
"@types/uuid": "^9.0.8",
"jest-environment-node": "^29.7.0",
"nock": "14.0.0-beta.19",
"node-mocks-http": "^1.16.2",
"testcontainers": "^10.16.0",
"ts-node-dev": "^2.0.0",
"nock": "14.0.0-beta.19"
"ts-node-dev": "^2.0.0"
}
}
24 changes: 23 additions & 1 deletion packages/card-service/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import Router from '@koa/router'
import bodyParser from 'koa-bodyparser'
import { Server } from 'http'
import cors from '@koa/cors'
import { createValidatorMiddleware, HttpMethod } from '@interledger/openapi'
import { PaymentContext } from './payment/types'
import { PaymentEventContext } from './payment/types'

export interface AppServices {
logger: Promise<Logger>
Expand Down Expand Up @@ -45,7 +48,26 @@ export class App {
ctx.status = 200
})

// Add routes here...
const openApi = await this.container.use('openApi')

const paymentRoutes = await this.container.use('paymentRoutes')
router.post<DefaultState, PaymentContext>(
'/payment',
createValidatorMiddleware<PaymentContext>(openApi.cardServerSpec, {
path: '/payment',
method: HttpMethod.POST
}),
paymentRoutes.create
)

router.post<DefaultState, PaymentEventContext>(
'/payment-event',
createValidatorMiddleware<PaymentEventContext>(openApi.cardServerSpec, {
path: '/payment-event',
method: HttpMethod.POST
}),
paymentRoutes.handlePaymentEvent
)

koa.use(cors())
koa.use(router.routes())
Expand Down
1 change: 1 addition & 0 deletions packages/card-service/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const Config = {
env: envString('NODE_ENV', 'development'),
cardServicePort: envInt('CARD_SERVICE_PORT', 3007),
cardServiceUrl: envString('CARD_SERVICE_URL', 'http://localhost:3007'),
cardPaymentTimeoutMS: envInt('CARD_PAYMENT_TIMEOUT_MS', 30000),
redisUrl: envString('REDIS_URL', 'redis://127.0.0.1:6379'),
redisTls: parseRedisTlsConfig(
process.env.REDIS_TLS_CA_FILE_PATH,
Expand Down
34 changes: 34 additions & 0 deletions packages/card-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { knex } from 'knex'
import { Model } from 'objection'
import Redis from 'ioredis'
import { createPOSStore, PosStoreService } from './pos-store/service'
import { createPaymentService } from './payment/service'
import { createPaymentRoutes } from './payment/routes'
import { createOpenAPI } from '@interledger/openapi'
import path from 'path'

export function initIocContainer(
config: typeof Config
Expand Down Expand Up @@ -76,6 +80,36 @@ export function initIocContainer(
return createPOSStore({ redis, logger })
})

container.singleton('openApi', async () => {
const cardServerSpec = await createOpenAPI(
path.resolve(__dirname, './openapi/specs/card-server.yaml')
)

return {
cardServerSpec
}
})

container.singleton(
'paymentService',
async (deps: IocContract<AppServices>) => {
return createPaymentService({
logger: await deps.use('logger'),
config: await deps.use('config')
})
}
)

container.singleton(
'paymentRoutes',
async (deps: IocContract<AppServices>) => {
return createPaymentRoutes({
logger: await deps.use('logger'),
paymentService: await deps.use('paymentService')
})
}
)

return container
}

Expand Down
120 changes: 120 additions & 0 deletions packages/card-service/src/openapi/specs/card-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
openapi: 3.0.3
info:
title: Card Service API
version: 1.0.0
paths:
/payment:
post:
summary: Accept a payment request from POS
operationId: createPayment
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- requestId
- card
- merchantWalletAddress
- incomingPaymentUrl
- date
- signature
# - terminalCert
- terminalId
properties:
requestId:
type: string
format: uuid
card:
type: object
required:
- walletAddress
- transactionCounter
- expiry
properties:
walletAddress:
type: string
format: uri
transactionCounter:
type: integer
expiry:
type: string
pattern: '^(0[1-9]|1[0-2])\/\d{2,4}$'
example: '12/25'
description: 'Card expiry in MM/YY or MM/YYYY format'
merchantWalletAddress:
type: string
format: uri
incomingPaymentUrl:
type: string
format: uri
date:
type: string
format: date-time
signature:
type: string
# terminalCert:
# type: string
terminalId:
type: string
format: uuid
responses:
'201':
description: Payment request accepted
content:
application/json:
schema:
type: object
properties:
requestId:
type: string
result:
type: string
enum:
- approved
'400':
description: Invalid request
'401':
description: Card expired or invalid signature
'500':
description: Internal server error
/payment-event:
post:
summary: Handle payment event result from backend
operationId: handlePaymentEvent
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- requestId
- outgoingPaymentId
- result
properties:
requestId:
type: string
format: uuid
outgoingPaymentId:
type: string
format: uuid
result:
type: object
required:
- code
properties:
code:
type: string
enum:
- completed
- card_expired
- invalid_signature
responses:
'202':
description: Payment event accepted and forwarded
'400':
description: Malformed request body
'404':
description: Request not found
21 changes: 21 additions & 0 deletions packages/card-service/src/payment/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class PaymentRouteError extends Error {
public status: number
public details?: Record<string, unknown>

constructor(
status: number,
message: string,
details?: Record<string, unknown>
) {
super(message)
this.name = 'PaymentRouteError'
this.status = status
this.details = details
}
}

export class PaymentTimeoutError extends PaymentRouteError {
constructor(message = 'Timeout waiting for payment-event') {
super(504, message)
}
}
Loading
Loading