Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e0940e9
feat: created the backbone for the card service (#3508)
oana-lolea Jul 7, 2025
b66c944
feat: initialize POS service (#3509)
oana-lolea Jul 8, 2025
0f94e3a
feat(card-service): card payments table (#3514)
lengyel-arpad85 Jul 8, 2025
e2ee2c6
feat(pos): create merchants table (#3519)
lengyel-arpad85 Jul 8, 2025
b5b401f
Docker compose fix (for slower PCs) on localenv (#3522)
oana-lolea Jul 8, 2025
dcf0f61
typo
lengyel-arpad85 Jul 8, 2025
3a80c74
Merge remote-tracking branch 'refs/remotes/origin/pos-card-services' …
lengyel-arpad85 Jul 8, 2025
3786e36
fix(cards-service): add dependencies to package.json (#3530)
beniaminmunteanu Jul 9, 2025
5f78feb
feat: Added CardService client in rafiki backend (#3510)
zeppelin44 Jul 9, 2025
93a0cb8
feat(pos): pos card services table (#3526)
lengyel-arpad85 Jul 9, 2025
7ade8bf
feat(card-service): introduce testcontainers for database and redis (…
beniaminmunteanu Jul 9, 2025
7758da5
feat(pos): merchants services (#3528)
lengyel-arpad85 Jul 10, 2025
18f31a8
middleware start
lengyel-arpad85 Jul 10, 2025
2d4fe93
feat: Integrate Redis client in Card Service for (requestId, posServi…
zeppelin44 Jul 10, 2025
a61b299
feat(pos): create merchant route (#3538)
lengyel-arpad85 Jul 10, 2025
d539180
Merge branch 'pos-card-services' into al/raf-1089-merchant-route-midd…
lengyel-arpad85 Jul 10, 2025
ba55097
feat(card-service): introduce AuditLogService (#3525)
beniaminmunteanu Jul 10, 2025
c249a7c
merchant middleware - incomplete
lengyel-arpad85 Jul 10, 2025
d260e6f
fix no any
lengyel-arpad85 Jul 10, 2025
2b43308
adjustments
lengyel-arpad85 Jul 10, 2025
ed3d49c
fix(localenv): add required env vars to docker-compose (#3550)
njlie Jul 10, 2025
c5b9b76
feat(point-of-sale): POS Device service (#3548)
oana-lolea Jul 10, 2025
a9f122d
format
lengyel-arpad85 Jul 10, 2025
59e5c00
Merge branch 'pos-card-services' into al/raf-1089-merchant-route-midd…
lengyel-arpad85 Jul 10, 2025
3a78f17
unused
lengyel-arpad85 Jul 10, 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
8 changes: 8 additions & 0 deletions localenv/admin-auth/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ CREATE USER cloud_nine_wallet_auth WITH PASSWORD 'cloud_nine_wallet_auth';
CREATE DATABASE cloud_nine_wallet_auth;
ALTER DATABASE cloud_nine_wallet_auth OWNER TO cloud_nine_wallet_auth;

CREATE USER cloud_nine_wallet_card WITH PASSWORD 'cloud_nine_wallet_card';
CREATE DATABASE cloud_nine_wallet_card;
ALTER DATABASE cloud_nine_wallet_card OWNER TO cloud_nine_wallet_card;

CREATE USER happy_life_bank_backend WITH PASSWORD 'happy_life_bank_backend';
CREATE DATABASE happy_life_bank_backend;
ALTER DATABASE happy_life_bank_backend OWNER TO happy_life_bank_backend;
Expand All @@ -14,6 +18,10 @@ CREATE USER happy_life_bank_auth WITH PASSWORD 'happy_life_bank_auth';
CREATE DATABASE happy_life_bank_auth;
ALTER DATABASE happy_life_bank_auth OWNER TO happy_life_bank_auth;

CREATE USER happy_life_bank_card WITH PASSWORD 'happy_life_bank_card';
CREATE DATABASE happy_life_bank_card;
ALTER DATABASE happy_life_bank_card OWNER TO happy_life_bank_card;

CREATE USER happy_life_kratos WITH PASSWORD 'kratos_password';
CREATE DATABASE happy_life_kratos;
ALTER DATABASE happy_life_kratos OWNER TO happy_life_kratos;
Expand Down
16 changes: 16 additions & 0 deletions localenv/cloud-nine-wallet/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@ CREATE USER cloud_nine_wallet_auth WITH PASSWORD 'cloud_nine_wallet_auth';
CREATE DATABASE cloud_nine_wallet_auth;
ALTER DATABASE cloud_nine_wallet_auth OWNER TO cloud_nine_wallet_auth;

CREATE USER cloud_nine_wallet_card_service WITH PASSWORD 'cloud_nine_wallet_card_service';
CREATE DATABASE cloud_nine_wallet_card_service;
ALTER DATABASE cloud_nine_wallet_card_service OWNER TO cloud_nine_wallet_card_service;

CREATE USER happy_life_bank_backend WITH PASSWORD 'happy_life_bank_backend';
CREATE DATABASE happy_life_bank_backend;
ALTER DATABASE happy_life_bank_backend OWNER TO happy_life_bank_backend;

CREATE USER happy_life_bank_auth WITH PASSWORD 'happy_life_bank_auth';
CREATE DATABASE happy_life_bank_auth;
ALTER DATABASE happy_life_bank_auth OWNER TO happy_life_bank_auth;

CREATE USER cloud_nine_wallet_point_of_sale WITH PASSWORD 'cloud_nine_wallet_point_of_sale';
CREATE DATABASE cloud_nine_wallet_point_of_sale;
ALTER DATABASE cloud_nine_wallet_point_of_sale OWNER TO cloud_nine_wallet_point_of_sale;

CREATE USER happy_life_bank_point_of_sale WITH PASSWORD 'happy_life_bank_point_of_sale';
CREATE DATABASE happy_life_bank_point_of_sale;
ALTER DATABASE happy_life_bank_point_of_sale OWNER TO happy_life_bank_point_of_sale;

CREATE USER happy_life_bank_card_service WITH PASSWORD 'happy_life_bank_card_service';
CREATE DATABASE happy_life_bank_card_service;
ALTER DATABASE happy_life_bank_card_service OWNER TO happy_life_bank_card_service;
65 changes: 65 additions & 0 deletions localenv/cloud-nine-wallet/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
name: c9
services:
cloud-nine-wallet-card-service:
hostname: cloud-nine-wallet-card-service
image: rafiki-card-service
build:
context: ../..
dockerfile: ./packages/card-service/Dockerfile.dev
restart: always
networks:
- rafiki
ports:
- '3007:3007'
volumes:
- type: bind
source: ../../packages/card-service/src
target: /home/rafiki/packages/card-service/src
read_only: true
environment:
NODE_ENV: ${NODE_ENV:-development}
INSTANCE_NAME: CLOUD-NINE
TRUST_PROXY: ${TRUST_PROXY}
LOG_LEVEL: debug
CARD_SERVICE_PORT: 3007
DATABASE_URL: postgresql://cloud_nine_wallet_card_service:cloud_nine_wallet_card_service@shared-database/cloud_nine_wallet_card_service
depends_on:
- shared-database
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3007/healthz"]
start_period: 60s
start_interval: 5s
interval: 30s
retries: 1
timeout: 3s
cloud-nine-wallet-point-of-sale:
hostname: cloud-nine-wallet-point-of-sale
image: rafiki-point-of-sale
build:
context: ../..
dockerfile: ./packages/point-of-sale/Dockerfile.dev
restart: always
networks:
- rafiki
ports:
- '3008:3008'
volumes:
- type: bind
source: ../../packages/point-of-sale/src
target: /home/rafiki/packages/point-of-sale/src
read_only: true
environment:
NODE_ENV: ${NODE_ENV:-development}
INSTANCE_NAME: CLOUD-NINE
TRUST_PROXY: ${TRUST_PROXY}
LOG_LEVEL: debug
PORT: 3008
DATABASE_URL: postgresql://cloud_nine_wallet_point_of_sale:cloud_nine_wallet_point_of_sale@shared-database/cloud_nine_wallet_point_of_sale
depends_on:
- shared-database
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3008/healthz"]
start_period: 60s
start_interval: 5s
interval: 30s
retries: 1
timeout: 3s
cloud-nine-mock-ase:
hostname: cloud-nine-wallet
image: rafiki-mock-ase
Expand Down Expand Up @@ -82,6 +146,7 @@ services:
ENABLE_TELEMETRY: true
KEY_ID: 7097F83B-CB84-469E-96C6-2141C72E22C0
OPERATOR_TENANT_ID: 438fa74a-fa7d-4317-9ced-dde32ece1787
CARD_SERVICE_HOST: 'http://cloud-nine-wallet-card-service:3007'
depends_on:
- shared-database
- shared-redis
Expand Down
67 changes: 67 additions & 0 deletions localenv/happy-life-bank/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,71 @@
name: hl
services:
happy-life-bank-card-service:
hostname: happy-life-bank-card-service
image: rafiki-card-service
build:
context: ../..
dockerfile: ./packages/card-service/Dockerfile.dev
restart: always
networks:
- rafiki
ports:
- '4007:4007'
volumes:
- type: bind
source: ../../packages/card-service/src
target: /home/rafiki/packages/card-service/src
read_only: true
environment:
NODE_ENV: ${NODE_ENV:-development}
INSTANCE_NAME: HAPPY-LIFE
TRUST_PROXY: ${TRUST_PROXY}
LOG_LEVEL: debug
CARD_SERVICE_PORT: 4007
DATABASE_URL: postgresql://happy_life_bank_card_service:happy_life_bank_card_service@shared-database/happy_life_bank_card_service
depends_on:
- shared-database
- cloud-nine-wallet-card-service
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3007/healthz"]
start_period: 60s
start_interval: 5s
interval: 30s
retries: 1
timeout: 3s
happy-life-bank-point-of-sale:
hostname: happy-life-bank-point-of-sale
image: rafiki-point-of-sale
build:
context: ../..
dockerfile: ./packages/point-of-sale/Dockerfile.dev
restart: always
networks:
- rafiki
ports:
- '4008:4008'
volumes:
- type: bind
source: ../../packages/point-of-sale/src
target: /home/rafiki/packages/point-of-sale/src
read_only: true
environment:
NODE_ENV: ${NODE_ENV:-development}
INSTANCE_NAME: HAPPY-LIFE
TRUST_PROXY: ${TRUST_PROXY}
LOG_LEVEL: debug
PORT: 4008
DATABASE_URL: postgresql://happy_life_bank_point_of_sale:happy_life_bank_point_of_sale@shared-database/happy_life_bank_point_of_sale
depends_on:
- shared-database
- cloud-nine-wallet-point-of-sale
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:4008/healthz"]
start_period: 60s
start_interval: 5s
interval: 30s
retries: 1
timeout: 3s
happy-life-mock-ase:
hostname: happy-life-bank
image: rafiki-mock-ase
Expand Down Expand Up @@ -75,6 +141,7 @@ services:
ENABLE_TELEMETRY: true
KEY_ID: 53f2d913-e98a-40b9-b270-372d0547f23d
OPERATOR_TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
CARD_SERVICE_HOST: 'http://happy-life-bank-card-service:4007'
depends_on:
- cloud-nine-backend
healthcheck:
Expand Down
1 change: 1 addition & 0 deletions packages/backend/jest.env.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ process.env.AUTH_ADMIN_API_SECRET = 'test-secret'
process.env.OPERATOR_TENANT_ID = 'cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d'
process.env.API_SECRET = 'KQEXlZO65jUJXakXnLxGO7dk387mt71G9tZ42rULSNU='
process.env.EXCHANGE_RATES_URL = 'http://example.com/rates'
process.env.CARD_SERVICE_HOST = 'http://127.0.0.1:3007'
71 changes: 71 additions & 0 deletions packages/backend/src/card/service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { AxiosInstance } from 'axios'
import { createCardService } from './service'
import { Logger } from 'pino'

describe('Card Service', () => {
let mockAxios: Partial<AxiosInstance>
let cardServiceHost: string
let mockLogger: Partial<Logger>
let cardService: Awaited<ReturnType<typeof createCardService>>

beforeAll(async () => {
mockAxios = {
post: jest.fn()
}
mockLogger = {
error: jest.fn(),
child: jest.fn().mockReturnThis()
}
cardServiceHost = 'http://card-service.test'
cardService = await createCardService({
axios: mockAxios as AxiosInstance,
cardServiceHost,
logger: mockLogger as Logger
})
})

afterEach(() => {
jest.clearAllMocks()
})

describe('sendPaymentEvent', () => {
const eventDetails = {
requestId: 'req-123',
outgoingPaymentId: 'out-456',
resultCode: 'completed'
}

test('sends payment event to the correct endpoint', async () => {
;(mockAxios.post as jest.Mock).mockResolvedValueOnce({ status: 200 })
await expect(
cardService.sendPaymentEvent(eventDetails)
).resolves.toBeUndefined()
expect(mockAxios.post).toHaveBeenCalledWith(
`${cardServiceHost}/payment-event`,
eventDetails
)
})

test('propagates errors from axios', async () => {
const error = new Error('network error')
;(mockAxios.post as jest.Mock).mockRejectedValueOnce(error)
await expect(cardService.sendPaymentEvent(eventDetails)).rejects.toThrow(
'network error'
)
})

test('logs and throws if response status is not 200', async () => {
;(mockAxios.post as jest.Mock).mockResolvedValueOnce({ status: 500 })
await expect(cardService.sendPaymentEvent(eventDetails)).rejects.toThrow(
`Failed to send payment event with details ${JSON.stringify(eventDetails)}`
)
expect(mockLogger.error).toHaveBeenCalledWith(
expect.objectContaining({
status: 500,
eventDetails
}),
'Failed to send payment event'
)
})
})
})
54 changes: 54 additions & 0 deletions packages/backend/src/card/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { AxiosInstance } from 'axios'
import { Logger } from 'pino'

const PAYMENT_FOUNDED_PATH = '/payment-event'

type EventDetails = {
requestId: string
outgoingPaymentId: string
resultCode: string
}

export interface CardService {
sendPaymentEvent(eventDetails: EventDetails): Promise<void>
}

interface ServiceDependencies {
axios: AxiosInstance
cardServiceHost: string
logger: Logger
}

export async function createCardService(
deps_: ServiceDependencies
): Promise<CardService> {
const logger = deps_.logger.child({
service: 'CardService'
})
const deps = {
...deps_,
logger
}

return {
sendPaymentEvent: (eventDetails: EventDetails) =>
sendPaymentEvent(deps, eventDetails)
}
}

async function sendPaymentEvent(
deps: ServiceDependencies,
eventDetails: EventDetails
) {
const { status } = await deps.axios.post(
`${deps.cardServiceHost}${PAYMENT_FOUNDED_PATH}`,
eventDetails
)

if (status !== 200) {
deps.logger.error({ status, eventDetails }, 'Failed to send payment event')
throw new Error(
`Failed to send payment event with details ${JSON.stringify(eventDetails)}`
)
}
}
3 changes: 2 additions & 1 deletion packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ export const Config = {
sendTenantWebhooksToOperator: envBool(
'SEND_TENANT_WEBHOOKS_TO_OPERATOR',
false
)
),
cardServiceHost: envString('CARD_SERVICE_HOST')
}

function parseRedisTlsConfig(
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ 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'
import { createCardService } from './card/service'

BigInt.prototype.toJSON = function () {
return this.toString()
Expand Down Expand Up @@ -671,6 +672,14 @@ export function initIocContainer(
})
})

container.singleton('cardService', async (deps) => {
return createCardService({
axios: await deps.use('axios'),
logger: await deps.use('logger'),
cardServiceHost: config.cardServiceHost
})
})

return container
}

Expand Down
Loading