From 34c9b722d18da08cf386a1fc40914e87accfecee Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:57:01 -0300 Subject: [PATCH 1/5] feat(pos-app): rename merchant API key to partner API key Update API key naming throughout POS app to use 'partner' terminology for consistency with platform branding. Changes include environment variables, secure storage keys, store state/methods, UI text, and documentation. Adds migration function to handle upgrade path for existing users. Co-Authored-By: Claude Haiku 4.5 --- dapps/pos-app/.env.example | 2 +- dapps/pos-app/AGENTS.md | 6 +- .../__tests__/services/payment.test.ts | 12 ++-- .../__tests__/store/useSettingsStore.test.ts | 38 +++++----- .../pos-app/__tests__/utils/store-helpers.ts | 10 +-- dapps/pos-app/app/_layout.tsx | 17 ++++- dapps/pos-app/app/index.tsx | 4 +- dapps/pos-app/app/settings.tsx | 24 +++---- dapps/pos-app/hooks/use-merchant-flow.ts | 70 +++++++++---------- dapps/pos-app/services/payment.ts | 8 +-- dapps/pos-app/services/payment.web.ts | 4 +- dapps/pos-app/store/useSettingsStore.ts | 51 ++++++-------- dapps/pos-app/utils/merchant-config.ts | 8 +-- dapps/pos-app/utils/secure-storage.ts | 18 ++++- dapps/pos-app/utils/secure-storage.web.ts | 19 ++++- 15 files changed, 163 insertions(+), 128 deletions(-) diff --git a/dapps/pos-app/.env.example b/dapps/pos-app/.env.example index 84b43916..2c0295ae 100644 --- a/dapps/pos-app/.env.example +++ b/dapps/pos-app/.env.example @@ -4,6 +4,6 @@ SENTRY_AUTH_TOKEN="" EXPO_PUBLIC_API_URL="" EXPO_PUBLIC_GATEWAY_URL="" EXPO_PUBLIC_DEFAULT_MERCHANT_ID="" -EXPO_PUBLIC_DEFAULT_MERCHANT_API_KEY="" +EXPO_PUBLIC_DEFAULT_PARTNER_API_KEY="" EXPO_PUBLIC_MERCHANT_API_URL="" EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY="" \ No newline at end of file diff --git a/dapps/pos-app/AGENTS.md b/dapps/pos-app/AGENTS.md index 8caeac76..608f0c4d 100644 --- a/dapps/pos-app/AGENTS.md +++ b/dapps/pos-app/AGENTS.md @@ -258,7 +258,7 @@ SENTRY_AUTH_TOKEN="" # Sentry authentication token EXPO_PUBLIC_API_URL="" # Payment API base URL EXPO_PUBLIC_GATEWAY_URL="" # WalletConnect gateway URL EXPO_PUBLIC_DEFAULT_MERCHANT_ID="" # Default merchant ID (optional) -EXPO_PUBLIC_DEFAULT_MERCHANT_API_KEY="" # Default merchant API key (optional) +EXPO_PUBLIC_DEFAULT_PARTNER_API_KEY="" # Default partner API key (optional) EXPO_PUBLIC_MERCHANT_API_URL="" # Merchant Portal API base URL EXPO_PUBLIC_MERCHANT_PORTAL_API_KEY="" # Merchant Portal API key (for Activity screen) ``` @@ -678,11 +678,11 @@ const { data, isLoading, error } = usePaymentStatus(paymentId, { import { secureStorage, SECURE_STORAGE_KEYS } from "@/utils/secure-storage"; // Store -await secureStorage.setItem(SECURE_STORAGE_KEYS.MERCHANT_API_KEY, apiKey); +await secureStorage.setItem(SECURE_STORAGE_KEYS.PARTNER_API_KEY, apiKey); // Retrieve const apiKey = await secureStorage.getItem( - SECURE_STORAGE_KEYS.MERCHANT_API_KEY, + SECURE_STORAGE_KEYS.PARTNER_API_KEY, ); ``` diff --git a/dapps/pos-app/__tests__/services/payment.test.ts b/dapps/pos-app/__tests__/services/payment.test.ts index 48ac2f63..a3506101 100644 --- a/dapps/pos-app/__tests__/services/payment.test.ts +++ b/dapps/pos-app/__tests__/services/payment.test.ts @@ -42,10 +42,10 @@ describe("Payment Service", () => { describe("getApiHeaders (via startPayment/getPaymentStatus)", () => { it("should throw error when merchant ID is not configured", async () => { // Set API key but not merchant ID - await SecureStore.setItemAsync("merchant_api_key", "test-api-key"); + await SecureStore.setItemAsync("partner_api_key", "test-api-key"); useSettingsStore.setState({ merchantId: null, - isMerchantApiKeySet: true, + isPartnerApiKeySet: true, }); await expect( @@ -57,10 +57,10 @@ describe("Payment Service", () => { }); it("should throw error when merchant ID is empty string", async () => { - await SecureStore.setItemAsync("merchant_api_key", "test-api-key"); + await SecureStore.setItemAsync("partner_api_key", "test-api-key"); useSettingsStore.setState({ merchantId: " ", // whitespace only - isMerchantApiKeySet: true, + isPartnerApiKeySet: true, }); await expect( @@ -74,7 +74,7 @@ describe("Payment Service", () => { it("should throw error when API key is not configured", async () => { useSettingsStore.setState({ merchantId: "merchant-123", - isMerchantApiKeySet: false, + isPartnerApiKeySet: false, }); // Don't set the API key in secure storage @@ -83,7 +83,7 @@ describe("Payment Service", () => { referenceId: "ref-123", amount: { value: "1000", unit: "cents" }, }), - ).rejects.toThrow("Merchant API key is not configured"); + ).rejects.toThrow("Partner API key is not configured"); }); it("should include correct headers when merchant is configured", async () => { diff --git a/dapps/pos-app/__tests__/store/useSettingsStore.test.ts b/dapps/pos-app/__tests__/store/useSettingsStore.test.ts index a14bd798..5eb7e35e 100644 --- a/dapps/pos-app/__tests__/store/useSettingsStore.test.ts +++ b/dapps/pos-app/__tests__/store/useSettingsStore.test.ts @@ -34,9 +34,9 @@ describe("useSettingsStore", () => { expect(merchantId).toBeNull(); }); - it("should have isMerchantApiKeySet as false", () => { - const { isMerchantApiKeySet } = useSettingsStore.getState(); - expect(isMerchantApiKeySet).toBe(false); + it("should have isPartnerApiKeySet as false", () => { + const { isPartnerApiKeySet } = useSettingsStore.getState(); + expect(isPartnerApiKeySet).toBe(false); }); it("should have zero failed PIN attempts", () => { @@ -155,57 +155,57 @@ describe("useSettingsStore", () => { }); }); - describe("setMerchantApiKey / clearMerchantApiKey / getMerchantApiKey", () => { + describe("setPartnerApiKey / clearPartnerApiKey / getPartnerApiKey", () => { it("should store API key in secure storage", async () => { - const { setMerchantApiKey } = useSettingsStore.getState(); + const { setPartnerApiKey } = useSettingsStore.getState(); - await setMerchantApiKey("api-key-123"); + await setPartnerApiKey("api-key-123"); - expect(useSettingsStore.getState().isMerchantApiKeySet).toBe(true); + expect(useSettingsStore.getState().isPartnerApiKeySet).toBe(true); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( - "merchant_api_key", + "partner_api_key", "api-key-123", ); }); it("should retrieve API key from secure storage", async () => { // First store the key - await useSettingsStore.getState().setMerchantApiKey("api-key-456"); + await useSettingsStore.getState().setPartnerApiKey("api-key-456"); // Then retrieve it - const apiKey = await useSettingsStore.getState().getMerchantApiKey(); + const apiKey = await useSettingsStore.getState().getPartnerApiKey(); expect(apiKey).toBe("api-key-456"); }); it("should clear API key from secure storage", async () => { // First store a key - await useSettingsStore.getState().setMerchantApiKey("api-key-789"); - expect(useSettingsStore.getState().isMerchantApiKeySet).toBe(true); + await useSettingsStore.getState().setPartnerApiKey("api-key-789"); + expect(useSettingsStore.getState().isPartnerApiKeySet).toBe(true); // Clear it - await useSettingsStore.getState().clearMerchantApiKey(); + await useSettingsStore.getState().clearPartnerApiKey(); - expect(useSettingsStore.getState().isMerchantApiKeySet).toBe(false); + expect(useSettingsStore.getState().isPartnerApiKeySet).toBe(false); expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith( - "merchant_api_key", + "partner_api_key", ); }); it("should remove API key when setting null", async () => { // First store a key - await useSettingsStore.getState().setMerchantApiKey("api-key-to-remove"); + await useSettingsStore.getState().setPartnerApiKey("api-key-to-remove"); // Set to null - await useSettingsStore.getState().setMerchantApiKey(null); + await useSettingsStore.getState().setPartnerApiKey(null); expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith( - "merchant_api_key", + "partner_api_key", ); }); it("should return null when no API key is stored", async () => { - const apiKey = await useSettingsStore.getState().getMerchantApiKey(); + const apiKey = await useSettingsStore.getState().getPartnerApiKey(); expect(apiKey).toBeNull(); }); }); diff --git a/dapps/pos-app/__tests__/utils/store-helpers.ts b/dapps/pos-app/__tests__/utils/store-helpers.ts index f25586ba..bda274ae 100644 --- a/dapps/pos-app/__tests__/utils/store-helpers.ts +++ b/dapps/pos-app/__tests__/utils/store-helpers.ts @@ -16,7 +16,7 @@ export function resetSettingsStore() { variant: "default", _hasHydrated: false, merchantId: null, - isMerchantApiKeySet: false, + isPartnerApiKeySet: false, pinFailedAttempts: 0, pinLockoutUntil: null, biometricEnabled: false, @@ -51,11 +51,11 @@ export async function setupTestMerchant( ): Promise<() => Promise> { // eslint-disable-next-line @typescript-eslint/no-require-imports const SecureStore = require("expo-secure-store"); - await SecureStore.setItemAsync("merchant_api_key", apiKey); + await SecureStore.setItemAsync("partner_api_key", apiKey); useSettingsStore.setState({ merchantId, - isMerchantApiKeySet: true, + isPartnerApiKeySet: true, }); // Return cleanup function for use in afterEach or manual cleanup @@ -68,10 +68,10 @@ export async function setupTestMerchant( export async function clearTestMerchant() { // eslint-disable-next-line @typescript-eslint/no-require-imports const SecureStore = require("expo-secure-store"); - await SecureStore.deleteItemAsync("merchant_api_key"); + await SecureStore.deleteItemAsync("partner_api_key"); useSettingsStore.setState({ merchantId: null, - isMerchantApiKeySet: false, + isPartnerApiKeySet: false, }); } diff --git a/dapps/pos-app/app/_layout.tsx b/dapps/pos-app/app/_layout.tsx index 1404ff9d..5beff6df 100644 --- a/dapps/pos-app/app/_layout.tsx +++ b/dapps/pos-app/app/_layout.tsx @@ -28,7 +28,10 @@ import { useLogsStore } from "@/store/useLogsStore"; import { useSettingsStore } from "@/store/useSettingsStore"; import { getDeviceIdentifier } from "@/utils/misc"; import { requestBluetoothPermission } from "@/utils/printer"; -import { clearStaleSecureStorage } from "@/utils/secure-storage"; +import { + clearStaleSecureStorage, + migratePartnerApiKey, +} from "@/utils/secure-storage"; import { showInfoToast } from "@/utils/toast"; import { toastConfig } from "@/utils/toasts"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -71,7 +74,17 @@ export default Sentry.wrap(function RootLayout() { }); useEffect(() => { - clearStaleSecureStorage(); + const initializeStorage = async () => { + clearStaleSecureStorage(); + await migratePartnerApiKey(); + + // Sync isPartnerApiKeySet flag after migration in case a key was migrated + const apiKey = await useSettingsStore.getState().getPartnerApiKey(); + if (apiKey && !useSettingsStore.getState().isPartnerApiKeySet) { + useSettingsStore.setState({ isPartnerApiKeySet: true }); + } + }; + initializeStorage(); }, []); useEffect(() => { diff --git a/dapps/pos-app/app/index.tsx b/dapps/pos-app/app/index.tsx index dcb8c164..33677607 100644 --- a/dapps/pos-app/app/index.tsx +++ b/dapps/pos-app/app/index.tsx @@ -17,10 +17,10 @@ export default function HomeScreen() { ]); const Theme = useTheme(); - const { merchantId, isMerchantApiKeySet } = useSettingsStore(); + const { merchantId, isPartnerApiKeySet } = useSettingsStore(); const handleStartPayment = () => { - if (!merchantId || !isMerchantApiKeySet) { + if (!merchantId || !isPartnerApiKeySet) { router.push("/settings"); showErrorToast("Merchant information not configured"); return; diff --git a/dapps/pos-app/app/settings.tsx b/dapps/pos-app/app/settings.tsx index fa8258b9..da8a540d 100644 --- a/dapps/pos-app/app/settings.tsx +++ b/dapps/pos-app/app/settings.tsx @@ -55,16 +55,16 @@ export default function SettingsScreen() { const { merchantIdInput, - merchantApiKeyInput, + partnerApiKeyInput, activeModal, pinError, isMerchantIdConfirmDisabled, - isMerchantApiKeyConfirmDisabled, - hasStoredMerchantApiKey, + isPartnerApiKeyConfirmDisabled, + hasStoredPartnerApiKey, handleMerchantIdInputChange, - handleMerchantApiKeyInputChange, + handlePartnerApiKeyInputChange, handleMerchantIdConfirm, - handleMerchantApiKeyConfirm, + handlePartnerApiKeyConfirm, handlePinVerifyComplete, handleBiometricAuthSuccess, handleBiometricAuthFailure, @@ -260,12 +260,12 @@ export default function SettingsScreen() {