From 6e1676428ef3fa1882a9724b3544a8a38c64b035 Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Mon, 3 Aug 2026 13:19:22 +0200 Subject: [PATCH 1/2] fix(qr-pay): key the success-screen receipt by the Manteca synthetic id Every receipt shared off the QR-pay success screen 404s. The "See receipt" button stamped the drawer with `qrPayment.externalId`, but the receipt lookup (`/receipt/?kind=QR_PAY` -> findMantecaIntentBySyntheticId) resolves an entryId only via `metadata.mantecaSyntheticId` or the `manteca-transfer:` idempotency key -- i.e. `qrPayment.id`. `externalId` is a UUID we mint in createQrPaymentSynthetic. It is stored in metadata.externalId but is never a lookup key, and because it is UUID-shaped it slips past the id-shape gate and misses silently instead of failing loudly. Activity rows already carry mantecaSyntheticId as their uuid, so this also makes the success screen agree with Activity. --- .../qr-pay/__tests__/qr-pay-states.test.tsx | 25 +++++++++++++++++++ src/app/(mobile-ui)/qr-pay/page.tsx | 7 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx b/src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx index d2c45a152f..c0f6f3e7c8 100644 --- a/src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx +++ b/src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx @@ -1052,6 +1052,31 @@ describe('GROUP 4: Success States', () => { expect(screen.getByText('Split this bill')).toBeInTheDocument() }) + test('See receipt opens the drawer keyed by the Manteca synthetic id, not externalId', async () => { + // getReceiptUrl builds /receipt/?kind=QR_PAY, and the backend resolves + // that id only via the Manteca synthetic id. Stamping externalId here 404s + // every shared receipt. + const openTransactionDetails = jest.fn() + mockUseTransactionDetailsDrawer.mockReturnValue({ + openTransactionDetails, + selectedTransaction: null, + isDrawerOpen: false, + closeTransactionDetails: jest.fn(), + }) + + await completeMantecaPayment() + + await waitFor(() => { + expect(screen.getByText('See receipt')).toBeInTheDocument() + }) + + await act(async () => { + fireEvent.click(screen.getByText('See receipt')) + }) + + expect(openTransactionDetails).toHaveBeenCalledWith(expect.objectContaining({ id: 'qp1' })) + }) + test('Manteca success, perk eligible shows hold-to-claim button', async () => { await completeMantecaPayment({ perk: { diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index 9aef2fb137..a921c9125e 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -1440,7 +1440,12 @@ export default function QRPayPage() { onClick={() => { const now = new Date() openTransactionDetails({ - id: qrPayment!.externalId, + // Manteca synthetic id — the only key the receipt + // lookup resolves (`metadata.mantecaSyntheticId` / + // `manteca-transfer:`), and what Activity rows + // already carry. `externalId` is UUID-shaped, so it + // slips past the id-shape gate and 404s silently. + id: qrPayment!.id, direction: 'qr_payment', userName: qrPayment!.details.merchant.name, fullName: qrPayment!.details.merchant.name, From 81df85023fd7e4ae180e4fed632fd1eb9a199dde Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Mon, 3 Aug 2026 13:30:30 +0200 Subject: [PATCH 2/2] docs(qr-pay): tighten the receipt-id comment to the two load-bearing facts --- src/app/(mobile-ui)/qr-pay/page.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx b/src/app/(mobile-ui)/qr-pay/page.tsx index a921c9125e..c3066e7e5f 100644 --- a/src/app/(mobile-ui)/qr-pay/page.tsx +++ b/src/app/(mobile-ui)/qr-pay/page.tsx @@ -1440,11 +1440,10 @@ export default function QRPayPage() { onClick={() => { const now = new Date() openTransactionDetails({ - // Manteca synthetic id — the only key the receipt - // lookup resolves (`metadata.mantecaSyntheticId` / - // `manteca-transfer:`), and what Activity rows - // already carry. `externalId` is UUID-shaped, so it - // slips past the id-shape gate and 404s silently. + // Manteca synthetic id — the only key /receipt/ + // resolves, and what Activity rows already carry. + // `externalId` is UUID-shaped, so it slips past the + // id-shape gate and 404s silently instead of erroring. id: qrPayment!.id, direction: 'qr_payment', userName: qrPayment!.details.merchant.name,