Skip to content

Commit 50efa05

Browse files
authored
fix(point-of-sale): added bruno script + added missing env variables to local docker compose file (#3629)
* Added bruno script + added missing env variables to local docker containers * Added missing env variables for card-service * Sent tenant id to backend for now, updated bruno req, fixed createIncPayment mutation * Removed unnecessary header * Fixed a test after mutation update * Updated generated graphql files * Fixed connection between payment service calls * Fixed env variables and mutation type for creating inc payment
1 parent 7002c47 commit 50efa05

File tree

9 files changed

+184
-61
lines changed

9 files changed

+184
-61
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
meta {
2+
name: Initiate Payment
3+
type: http
4+
seq: 3
5+
}
6+
7+
post {
8+
url: http://localhost:4008/payment
9+
body: json
10+
auth: inherit
11+
}
12+
13+
body:json {
14+
{
15+
"card": {
16+
"walletAddress": "http://cloud-nine-wallet-backend/accounts/gfranklin",
17+
"trasactionCounter": 1,
18+
"expiry": "2025-09-13T13:00:00Z"
19+
},
20+
"signature": "signature",
21+
"value": 1,
22+
"merchantWalletAddress": "http://happy-life-bank-backend/accounts/pfry"
23+
}
24+
}

localenv/cloud-nine-wallet/docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ services:
1111
- rafiki
1212
ports:
1313
- '3007:3007'
14+
- '9234:9229'
1415
volumes:
1516
- type: bind
1617
source: ../../packages/card-service/src
@@ -23,6 +24,10 @@ services:
2324
LOG_LEVEL: debug
2425
CARD_SERVICE_PORT: 3007
2526
DATABASE_URL: postgresql://cloud_nine_wallet_card_service:cloud_nine_wallet_card_service@shared-database/cloud_nine_wallet_card_service
27+
GRAPHQL_URL: http://cloud-nine-wallet-backend:3001/graphql
28+
TENANT_ID: 438fa74a-fa7d-4317-9ced-dde32ece1787
29+
TENANT_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
30+
TENANT_SIGNATURE_VERSION: 1
2631
depends_on:
2732
- shared-database
2833
healthcheck:
@@ -55,6 +60,10 @@ services:
5560
LOG_LEVEL: debug
5661
PORT: 3008
5762
DATABASE_URL: postgresql://cloud_nine_wallet_point_of_sale:cloud_nine_wallet_point_of_sale@shared-database/cloud_nine_wallet_point_of_sale
63+
TENANT_ID: 438fa74a-fa7d-4317-9ced-dde32ece1787
64+
TENANT_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
65+
GRAPHQL_URL: http://cloud-nine-wallet-backend:3001/graphql
66+
WEBHOOK_SIGNATURE_SECRET: webhook_secret
5867
depends_on:
5968
- shared-database
6069
healthcheck:

localenv/happy-life-bank/docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ services:
2323
LOG_LEVEL: debug
2424
CARD_SERVICE_PORT: 4007
2525
DATABASE_URL: postgresql://happy_life_bank_card_service:happy_life_bank_card_service@shared-database/happy_life_bank_card_service
26+
GRAPHQL_URL: http://happy-life-bank-backend:3001/graphql
27+
TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
28+
TENANT_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
29+
TENANT_SIGNATURE_VERSION: 1
2630
depends_on:
2731
- shared-database
2832
- cloud-nine-wallet-card-service
@@ -44,6 +48,7 @@ services:
4448
- rafiki
4549
ports:
4650
- '4008:4008'
51+
- '9233:9229'
4752
volumes:
4853
- type: bind
4954
source: ../../packages/point-of-sale/src
@@ -56,6 +61,10 @@ services:
5661
LOG_LEVEL: debug
5762
PORT: 4008
5863
DATABASE_URL: postgresql://happy_life_bank_point_of_sale:happy_life_bank_point_of_sale@shared-database/happy_life_bank_point_of_sale
64+
TENANT_ID: cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d
65+
TENANT_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
66+
GRAPHQL_URL: http://happy-life-bank-backend:3001/graphql
67+
WEBHOOK_SIGNATURE_SECRET: webhook_secret
5968
depends_on:
6069
- shared-database
6170
- cloud-nine-wallet-point-of-sale

packages/point-of-sale/src/card-service-client/client.test.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import nock from 'nock'
99
import { HttpStatusCode } from 'axios'
1010
import { initIocContainer } from '..'
1111
import { Config } from '../config/app'
12+
import { faker } from '@faker-js/faker'
1213

1314
describe('CardServiceClient', () => {
1415
const CARD_SERVICE_URL = 'http://card-service.com'
@@ -38,9 +39,7 @@ describe('CardServiceClient', () => {
3839
date: new Date(),
3940
signature: '',
4041
card: {
41-
walletAddress: {
42-
cardService: CARD_SERVICE_URL
43-
},
42+
walletAddress: faker.internet.url(),
4443
trasactionCounter: 1,
4544
expiry: new Date(new Date().getDate() + 1)
4645
},
@@ -61,13 +60,17 @@ describe('CardServiceClient', () => {
6160
nock(CARD_SERVICE_URL)
6261
.post('/payment')
6362
.reply(response.code, createPaymentResponse(response.result))
64-
expect(await client.sendPayment(options)).toBe(response.result)
63+
expect(await client.sendPayment(CARD_SERVICE_URL, options)).toBe(
64+
response.result
65+
)
6566
})
6667
})
6768

6869
test('throws when there is no payload data', async () => {
6970
nock(CARD_SERVICE_URL).post('/payment').reply(HttpStatusCode.Ok, undefined)
70-
await expect(client.sendPayment(options)).rejects.toMatchObject({
71+
await expect(
72+
client.sendPayment(CARD_SERVICE_URL, options)
73+
).rejects.toMatchObject({
7174
status: HttpStatusCode.NotFound,
7275
message: 'No payment information was received'
7376
})
@@ -77,7 +80,9 @@ describe('CardServiceClient', () => {
7780
nock(CARD_SERVICE_URL)
7881
.post('/payment')
7982
.reply(HttpStatusCode.ServiceUnavailable, 'Something went wrong')
80-
await expect(client.sendPayment(options)).rejects.toMatchObject({
83+
await expect(
84+
client.sendPayment(CARD_SERVICE_URL, options)
85+
).rejects.toMatchObject({
8186
status: HttpStatusCode.ServiceUnavailable,
8287
message: 'Something went wrong'
8388
})

packages/point-of-sale/src/card-service-client/client.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import { BaseService } from '../shared/baseService'
1111
interface Card {
1212
trasactionCounter: number
1313
expiry: Date
14-
// TODO: replace with WalletAddress from payment service
15-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
16-
walletAddress: any
14+
walletAddress: string
1715
}
1816

1917
export interface PaymentOptions {
@@ -30,7 +28,7 @@ export interface PaymentOptions {
3028
}
3129

3230
export interface CardServiceClient {
33-
sendPayment(options: PaymentOptions): Promise<Result>
31+
sendPayment(cardServiceUrl: string, options: PaymentOptions): Promise<Result>
3432
}
3533

3634
interface ServiceDependencies extends BaseService {
@@ -70,12 +68,14 @@ export async function createCardServiceClient({
7068
axios
7169
}
7270
return {
73-
sendPayment: (options) => sendPayment(deps, options)
71+
sendPayment: (cardServiceUrl, options) =>
72+
sendPayment(deps, cardServiceUrl, options)
7473
}
7574
}
7675

7776
async function sendPayment(
7877
deps: ServiceDependencies,
78+
cardServiceUrl: string,
7979
options: PaymentOptions
8080
): Promise<Result> {
8181
try {
@@ -88,9 +88,8 @@ async function sendPayment(
8888
...options,
8989
requestId: uuid()
9090
}
91-
const cardServiceUrl = options.card.walletAddress.cardService
9291
const response = await deps.axios.post<PaymentResponse>(
93-
`${cardServiceUrl}/payment`,
92+
`${cardServiceUrl + (cardServiceUrl.endsWith('/') ? 'payment' : '/payment')}`,
9493
requestBody,
9594
config
9695
)
@@ -103,6 +102,7 @@ async function sendPayment(
103102
}
104103
return payment.result
105104
} catch (error) {
105+
deps.logger.debug(error)
106106
if (error instanceof CardServiceClientError) throw error
107107

108108
if (error instanceof AxiosError) {

packages/point-of-sale/src/payments/routes.test.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { PaymentService } from './service'
1010
import { CardServiceClient, Result } from '../card-service-client/client'
1111
import { createContext } from '../tests/context'
1212
import { CardServiceClientError } from '../card-service-client/errors'
13-
import { IncomingPaymentState } from '../graphql/generated/graphql'
1413
import { webhookWaitMap } from '../webhook-handlers/request-map'
1514
import { faker } from '@faker-js/faker'
1615
import { withConfigOverride } from '../tests/helpers'
@@ -154,17 +153,11 @@ describe('Payment Routes', () => {
154153
.spyOn(paymentService, 'createIncomingPayment')
155154
.mockResolvedValueOnce({
156155
id: 'incoming-payment-url',
157-
url: faker.internet.url(),
158-
createdAt: new Date().toString(),
159-
walletAddressId: v4(),
160-
expiresAt: new Date(Date.now() + 30000).toString(),
161-
receivedAmount: {
162-
assetCode: 'USD',
163-
assetScale: 2,
164-
value: BigInt(0)
165-
},
166-
state: IncomingPaymentState.Pending
156+
url: faker.internet.url()
167157
})
158+
jest
159+
.spyOn(paymentService, 'getWalletAddressIdByUrl')
160+
.mockResolvedValueOnce(faker.internet.url())
168161
}
169162
})
170163
})

packages/point-of-sale/src/payments/routes.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,16 @@ async function payment(
7171
assetScale: walletAddress.assetScale,
7272
value: body.value
7373
}
74+
// TODO: in the future we need to find a way to make it work in local playground
75+
const walletAddressUrl = body.merchantWalletAddress.replace(
76+
/^http:/,
77+
'https:'
78+
)
79+
const walletAddressId =
80+
await deps.paymentService.getWalletAddressIdByUrl(walletAddressUrl)
81+
7482
const incomingPayment = await deps.paymentService.createIncomingPayment(
75-
walletAddress.id,
83+
walletAddressId,
7684
incomingAmount
7785
)
7886
const deferred = new Deferred<WebhookBody>()
@@ -81,17 +89,21 @@ async function payment(
8189
deferred,
8290
deps.config.webhookTimeoutMs
8391
)
84-
const result = await deps.cardServiceClient.sendPayment({
85-
merchantWalletAddress: body.merchantWalletAddress,
86-
incomingPaymentUrl: incomingPayment.url,
87-
date: new Date(),
88-
signature: body.signature,
89-
card: body.card,
90-
incomingAmount: {
91-
...incomingAmount,
92-
value: incomingAmount.value.toString()
92+
93+
const result = await deps.cardServiceClient.sendPayment(
94+
walletAddress.cardService,
95+
{
96+
merchantWalletAddress: body.merchantWalletAddress,
97+
incomingPaymentUrl: incomingPayment.url,
98+
date: new Date(),
99+
signature: body.signature,
100+
card: body.card,
101+
incomingAmount: {
102+
...incomingAmount,
103+
value: incomingAmount.value.toString()
104+
}
93105
}
94-
})
106+
)
95107

96108
if (result !== Result.APPROVED) throw new InvalidCardPaymentError(result)
97109
const event = await waitForIncomingPaymentEvent(deps.config, deferred)
@@ -101,6 +113,7 @@ async function payment(
101113
ctx.body = result
102114
ctx.status = 200
103115
} catch (err) {
116+
deps.logger.debug(err)
104117
if (err instanceof IncomingPaymentEventTimeoutError)
105118
webhookWaitMap.delete(err.incomingPaymentId)
106119
const { body, status } = handlePaymentError(err)

packages/point-of-sale/src/payments/service.test.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ describe('createPaymentService', () => {
4343
const expectedUrl = 'https://api.example.com/incoming-payments/abc123'
4444
mockApolloClient.mutate = jest.fn().mockResolvedValue({
4545
data: {
46-
payment: {
47-
id: uuid,
48-
url: expectedUrl
46+
createIncomingPayment: {
47+
payment: {
48+
id: uuid,
49+
url: expectedUrl
50+
}
4951
}
5052
}
5153
})
@@ -68,20 +70,22 @@ describe('createPaymentService', () => {
6870
expect(mockApolloClient.mutate).toHaveBeenCalledWith(
6971
expect.objectContaining({
7072
variables: expect.objectContaining({
71-
walletAddressId,
72-
incomingAmount,
73-
idempotencyKey: expect.any(String),
74-
isCardPayment: true,
75-
expiresAt
73+
input: expect.objectContaining({
74+
expiresAt,
75+
idempotencyKey: expect.any(String),
76+
incomingAmount,
77+
isCardPayment: true,
78+
walletAddressId
79+
})
7680
})
7781
})
7882
)
7983
})
8084

8185
it('should throw and log error if payment creation fails (no id)', async () => {
82-
mockApolloClient.mutate = jest
83-
.fn()
84-
.mockResolvedValue({ data: { payment: undefined } })
86+
mockApolloClient.mutate = jest.fn().mockResolvedValue({
87+
data: { createIncomingPayment: { payment: undefined } }
88+
})
8589
const service = createPaymentService(deps)
8690
const walletAddressId = 'wallet-123'
8791
const incomingAmount: AmountInput = {
@@ -159,3 +163,35 @@ describe('getWalletAddress', () => {
159163
)
160164
})
161165
})
166+
167+
describe('getWalletAddressByUrl', () => {
168+
let service: PaymentService
169+
const WALLET_ADDRESS_URL = 'https://api.example.com/wallet-address'
170+
171+
beforeAll(() => {
172+
service = createPaymentService(deps)
173+
})
174+
175+
beforeEach(() => {
176+
jest.clearAllMocks()
177+
})
178+
179+
test('should obtain wallet address id successfully', async () => {
180+
const id = uuid()
181+
mockApolloClient.query = jest.fn().mockResolvedValue({
182+
data: { walletAddressByUrl: { id } }
183+
})
184+
const walletAddressId =
185+
await service.getWalletAddressIdByUrl(WALLET_ADDRESS_URL)
186+
expect(walletAddressId).toBe(id)
187+
})
188+
189+
test('should throw when no wallet address was found', async () => {
190+
mockApolloClient.query = jest.fn().mockResolvedValue({
191+
data: { walletAddressByUrl: undefined }
192+
})
193+
await expect(
194+
service.getWalletAddressIdByUrl(WALLET_ADDRESS_URL)
195+
).rejects.toThrow('Wallet address not found')
196+
})
197+
})

0 commit comments

Comments
 (0)