Skip to content

Commit 2450da6

Browse files
feat(pos-app): unify merchant API base URL, keep separate auth
Consolidate to single base URL (EXPO_PUBLIC_API_URL) for both Payment and Merchant APIs, removing EXPO_PUBLIC_MERCHANT_API_URL. Delete redundant merchant-client.ts and reuse apiClient. Extract getApiHeaders() to shared client module for payment endpoints. Merchant endpoints retain separate auth via EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY since the Merchants API has its own auth layer. Added TODOs for when APIs unify auth. Fix incomplete test mock after module export migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 20bbc89 commit 2450da6

File tree

9 files changed

+59
-207
lines changed

9 files changed

+59
-207
lines changed

dapps/pos-app/.env.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ EXPO_PUBLIC_API_URL=""
55
EXPO_PUBLIC_GATEWAY_URL=""
66
EXPO_PUBLIC_DEFAULT_MERCHANT_ID=""
77
EXPO_PUBLIC_DEFAULT_PARTNER_API_KEY=""
8-
EXPO_PUBLIC_MERCHANT_API_URL=""
9-
EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY=""
8+
EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY=""

dapps/pos-app/AGENTS.md

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Uses **Expo Router** with file-based routing:
129129
- Filter tabs: All, Failed, Pending, Completed
130130
- Transaction detail modal on tap
131131
- Empty state when no transactions
132-
- Uses Merchant Portal API for data fetching
132+
- Uses unified API for data fetching
133133

134134
### 2. Receipt Printing
135135

@@ -174,9 +174,10 @@ Uses **Expo Router** with file-based routing:
174174

175175
## Payment API Integration
176176

177-
### Payment API Client (`services/client.ts`)
177+
### API Client (`services/client.ts`)
178178

179179
- Base URL from `EXPO_PUBLIC_API_URL` environment variable
180+
- Shared `getApiHeaders()` helper for authenticated requests
180181
- Request/response interceptors
181182
- Error handling
182183

@@ -204,33 +205,24 @@ All Payment API requests include:
204205
- `Sdk-Version`: "1.0.0"
205206
- `Sdk-Platform`: "react-native"
206207

207-
## Merchant Portal API Integration
208-
209-
The Merchant Portal API is a separate backend used for fetching transaction history (Activity screen).
210-
211-
### Merchant API Client (`services/merchant-client.ts`)
212-
213-
- Base URL from `EXPO_PUBLIC_MERCHANT_API_URL` environment variable
214-
- Generic HTTP client (no credentials baked in)
215-
- API key passed per-request via headers
216-
- Used by native apps (iOS/Android) for direct API calls
217-
218-
### Server-Side Proxy (`api/transactions.ts`)
219-
220-
- Vercel serverless function that proxies requests to the Merchant Portal API (web only)
221-
- API key read from server-side env (`EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY`)
222-
- Client only sends `x-merchant-id` header
223-
- Avoids CORS issues by making requests server-side
224-
225208
### Transactions Service (`services/transactions.ts`)
226209

210+
> **Note:** The Merchants API currently has its own auth layer separate from the Payment API. Both share the same base URL (`EXPO_PUBLIC_API_URL`), but merchant endpoints authenticate via `EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY` (sent as `x-api-key` header) rather than the partner API key used by payment endpoints. This will be unified in the future.
211+
227212
**`getTransactions(options)`**
228213

229214
- Fetches merchant transaction history
230215
- Endpoint: `GET /merchants/{merchant_id}/payments`
216+
- Uses the shared base URL (`EXPO_PUBLIC_API_URL`) but authenticates with `EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY`
231217
- Supports filtering by status, date range, pagination
232218
- Returns array of `PaymentRecord` objects
233219

220+
### Server-Side Proxy (`api/transactions.ts`)
221+
222+
- Vercel serverless function that proxies transaction requests (web only)
223+
- Client only sends `x-merchant-id` header; API key is handled server-side via `EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY`
224+
- Avoids CORS issues by making requests server-side
225+
234226
### useTransactions Hook (`services/hooks.ts`)
235227

236228
```typescript
@@ -259,7 +251,6 @@ EXPO_PUBLIC_API_URL="" # Payment API base URL
259251
EXPO_PUBLIC_GATEWAY_URL="" # WalletConnect gateway URL
260252
EXPO_PUBLIC_DEFAULT_MERCHANT_ID="" # Default merchant ID (optional)
261253
EXPO_PUBLIC_DEFAULT_PARTNER_API_KEY="" # Default partner API key (optional)
262-
EXPO_PUBLIC_MERCHANT_API_URL="" # Merchant Portal API base URL
263254
EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY="" # Merchant Portal API key (for Activity screen)
264255
```
265256

@@ -330,8 +321,7 @@ This project uses **npm** (not pnpm or yarn). Always use `npm` commands for inst
330321

331322
### Services & API
332323

333-
- **`services/client.ts`**: Payment API client configuration
334-
- **`services/merchant-client.ts`**: Merchant Portal API client (native)
324+
- **`services/client.ts`**: API client and shared auth headers (`getApiHeaders`)
335325
- **`services/payment.ts`**: Payment API functions
336326
- **`services/transactions.ts`**: Transaction fetching (native: direct API)
337327
- **`services/transactions.web.ts`**: Transaction fetching (web: server-side proxy)
@@ -713,10 +703,13 @@ const apiKey = await secureStorage.getItem(SECURE_STORAGE_KEYS.PARTNER_API_KEY);
713703
npm run lint # Check and fix ESLint errors
714704
npx prettier --write . # Format code with Prettier
715705
npx tsc --noEmit # Check for TypeScript errors
706+
npm test # Run Jest tests
716707
```
717708

718709
Fix any errors found. Pre-existing TypeScript errors in unrelated files can be ignored.
719710

711+
**When moving exports between modules**, update any `jest.mock()` calls in tests that mock the source or destination module. Mocks that use a manual factory (e.g., `jest.mock("@/services/client", () => ({ ... }))`) replace the entire module — any export not included in the factory becomes `undefined` at runtime, which silently breaks tests.
712+
720713
### Code Style
721714

722715
- Follow TypeScript best practices

dapps/pos-app/__tests__/services/payment.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import { apiClient } from "@/services/client";
1717
// Get the mocked secure store
1818
const SecureStore = require("expo-secure-store");
1919

20-
// Mock the API client
20+
// Mock only apiClient, keep real getApiHeaders so header logic is tested
2121
jest.mock("@/services/client", () => ({
22+
...jest.requireActual("@/services/client"),
2223
apiClient: {
2324
get: jest.fn(),
2425
post: jest.fn(),

dapps/pos-app/api/transactions.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { VercelRequest, VercelResponse } from "@vercel/node";
22

3-
const MERCHANT_API_BASE_URL = process.env.EXPO_PUBLIC_MERCHANT_API_URL;
3+
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL;
4+
// TODO: Once Merchants API unifies auth with Payment API, forward client credentials instead
45
const MERCHANT_PORTAL_API_KEY = process.env.EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY;
56

67
/**
@@ -25,9 +26,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
2526
});
2627
}
2728

28-
if (!MERCHANT_API_BASE_URL) {
29+
if (!API_BASE_URL) {
2930
return res.status(500).json({
30-
message: "MERCHANT_API_BASE_URL is not configured",
31+
message: "API_BASE_URL is not configured",
3132
});
3233
}
3334

@@ -63,7 +64,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
6364
}
6465

6566
const queryString = params.toString();
66-
const normalizedBaseUrl = MERCHANT_API_BASE_URL.replace(/\/+$/, "");
67+
const normalizedBaseUrl = API_BASE_URL.replace(/\/+$/, "");
6768
const endpoint = `/merchants/${encodeURIComponent(merchantId)}/payments${queryString ? `?${queryString}` : ""}`;
6869

6970
const response = await fetch(`${normalizedBaseUrl}${endpoint}`, {

dapps/pos-app/services/client.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useLogsStore } from "@/store/useLogsStore";
2+
import { useSettingsStore } from "@/store/useSettingsStore";
23
import { ApiError } from "@/utils/types";
34

45
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL;
@@ -156,3 +157,29 @@ class ApiClient {
156157
}
157158

158159
export const apiClient = new ApiClient(API_BASE_URL);
160+
161+
/**
162+
* Get API headers for authenticated requests
163+
* @returns Headers object with Api-Key, Merchant-Id, and SDK headers
164+
* @throws Error if partner API key or merchant ID is missing
165+
*/
166+
export async function getApiHeaders(): Promise<Record<string, string>> {
167+
const merchantId = useSettingsStore.getState().merchantId;
168+
const partnerApiKey = await useSettingsStore.getState().getPartnerApiKey();
169+
170+
if (!merchantId || merchantId.trim().length === 0) {
171+
throw new Error("Merchant ID is not configured");
172+
}
173+
174+
if (!partnerApiKey || partnerApiKey.trim().length === 0) {
175+
throw new Error("Partner API key is not configured");
176+
}
177+
178+
return {
179+
"Api-Key": partnerApiKey,
180+
"Merchant-Id": merchantId,
181+
"Sdk-Name": "pos-device",
182+
"Sdk-Version": "1.0.0",
183+
"Sdk-Platform": "react-native",
184+
};
185+
}

dapps/pos-app/services/merchant-client.ts

Lines changed: 0 additions & 144 deletions
This file was deleted.

dapps/pos-app/services/payment.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,9 @@
1-
import { useSettingsStore } from "@/store/useSettingsStore";
21
import {
32
PaymentStatusResponse,
43
StartPaymentRequest,
54
StartPaymentResponse,
65
} from "@/utils/types";
7-
import { apiClient } from "./client";
8-
9-
/**
10-
* Get API headers for authenticated requests
11-
* @returns Headers object with Api-Key, Merchant-Id, and SDK headers
12-
* @throws Error if partner API key or merchant ID is missing
13-
*/
14-
async function getApiHeaders(): Promise<Record<string, string>> {
15-
const merchantId = useSettingsStore.getState().merchantId;
16-
const partnerApiKey = await useSettingsStore.getState().getPartnerApiKey();
17-
18-
if (!merchantId || merchantId.trim().length === 0) {
19-
throw new Error("Merchant ID is not configured");
20-
}
21-
22-
if (!partnerApiKey || partnerApiKey.trim().length === 0) {
23-
throw new Error("Partner API key is not configured");
24-
}
25-
26-
return {
27-
"Api-Key": partnerApiKey,
28-
"Merchant-Id": merchantId,
29-
"Sdk-Name": "pos-device",
30-
"Sdk-Version": "1.0.0",
31-
"Sdk-Platform": "react-native",
32-
};
33-
}
6+
import { apiClient, getApiHeaders } from "./client";
347

358
/**
369
* Start a new payment

dapps/pos-app/services/transactions.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useSettingsStore } from "@/store/useSettingsStore";
22
import { TransactionsResponse } from "@/utils/types";
3-
import { merchantApiClient } from "./merchant-client";
3+
// TODO: Once Merchants API unifies auth with Payment API, switch to getApiHeaders()
4+
import { apiClient } from "./client";
5+
6+
const MERCHANT_PORTAL_API_KEY = process.env.EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY;
47

58
export interface GetTransactionsOptions {
69
status?: string | string[];
@@ -10,10 +13,8 @@ export interface GetTransactionsOptions {
1013
cursor?: string;
1114
}
1215

13-
const MERCHANT_PORTAL_API_KEY = process.env.EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY;
14-
1516
/**
16-
* Fetch merchant transactions from the Merchant Portal API (native version)
17+
* Fetch merchant transactions from the API (native version)
1718
* @param options - Optional query parameters for filtering and pagination
1819
* @returns TransactionsResponse with list of payments and stats
1920
*/
@@ -60,7 +61,7 @@ export async function getTransactions(
6061
const queryString = params.toString();
6162
const endpoint = `/merchants/${merchantId}/payments${queryString ? `?${queryString}` : ""}`;
6263

63-
return merchantApiClient.get<TransactionsResponse>(endpoint, {
64+
return apiClient.get<TransactionsResponse>(endpoint, {
6465
headers: {
6566
"x-api-key": MERCHANT_PORTAL_API_KEY,
6667
},

0 commit comments

Comments
 (0)