Skip to content

Instantly share code, notes, and snippets.

@ricbermo
Created August 5, 2026 15:44
Show Gist options
  • Select an option

  • Save ricbermo/8b951c4ce290e4c1eecf215a110afe05 to your computer and use it in GitHub Desktop.

Select an option

Save ricbermo/8b951c4ce290e4c1eecf215a110afe05 to your computer and use it in GitHub Desktop.
Research: return and notification refresh strategy

Return And Notification Refresh Strategy

Decision

Use server-authoritative, event-triggered SWR revalidation. Do not add timers, refreshInterval, client payment-deadline logic, or mobile calls to ePayco.

The product API owns payment settlement: signed ePayco confirmation is normal authority; it permits one bounded stale-PSE reference validation after 20 pending minutes and explicitly forbids periodic provider polling [1]. Mobile only reads Nursy API resources after an app event.

Required Contract

The server must return the service and payment identifiers in assignment and PSE-creation responses, return bank_url for a pending PSE attempt, and send payment_deadline_at plus terminal payment/service states [1]. PSE bank return and OneSignal payloads must contain only opaque routing identifiers:

nursy://payment-return?service_id=<id>&payment_id=<id>
{ "event": "payment.updated", "service_id": "<id>", "payment_id": "<id>" }

Neither input grants a status transition. Never put an ePayco state, amount, card data, PSE bank data, or personally identifying information in the URL or push payload. The next Nursy API read remains authority.

Event Flows

PSE Bank Return

  1. After PSE creation, open server-provided bank_url; persist local pending context only for routing, not payment status.
  2. Configure ePayco's return target as nursy://payment-return with opaque identifiers. Existing app config already declares nursy scheme [2].
  3. Add an Expo Router return route. Expo Router handles routes from incoming links; use +native-intent only if provider URL must be rewritten. That API runs outside app/auth context, so it should only normalize path safely [3].
  4. On return-route mount, call SWR mutate once for that service/payment cache key, then replace route with payment/service detail. If API remains pending, render pending and wait for a later server event or user refresh. Do not retry or schedule a follow-up fetch.

This handles cold launch and foreground return via same route. A browser back without deep link does not claim settlement; retain pending state and expose manual refresh.

Post-Assignment

Assignment response is immediate server truth. Write it into relevant SWR cache entries or call one mutate per affected service and payment key before navigating. For PSE, route to pending-payment UI after receiving bank_url. For cash/card, route from returned authoritative service/payment state.

Mobile already exposes payment data through SWR at /account_patient/payments; hooks return each hook's mutate [4]. There is no SWRConfig, refreshInterval, or React Native foreground revalidation currently configured [5]. Add a narrowly scoped revalidation helper at implementation time rather than a global focus handler. SWR supports programmatic mutate for one key or a key matcher [6]; prefer exact keys to avoid refreshing unrelated account data.

Notification

Server sends a OneSignal notification after its own payment/service transition (approved, declined, failed, reminder, or canceled_unpaid) [1].

  1. Register one root-level OneSignal click listener.
  2. Validate payload shape, navigate to service/payment detail, then revalidate affected exact SWR keys once.
  3. When a relevant notification arrives foreground, revalidate same keys once; normal display behavior stays unchanged.
  4. Remove listener with same callback during cleanup. OneSignal click listeners are additive and require paired removal [7].

Current onNotificationOpened creates an anonymous listener and returns no unsubscribe; it is not suitable as lifecycle owner without adjustment [8].

Invalidation Matrix

Trigger Navigation Refresh Prohibited
Assignment response Payment/service destination Exact affected service/payment keys once Timer; provider query
PSE deep-link return Return route, then detail Exact service/payment keys once Trust URL status; retry loop
OneSignal click Detail route Exact service/payment keys once Trust push status
Relevant foreground push Current UI remains Exact service/payment keys once Global account revalidation
Browser back/no return link Pending UI remains User-triggered refresh only Infer success; background polling

Implementation Boundaries

  • No client-side ePayco request, polling interval, timeout retry, or payment deadline clock.
  • No client mutation of payment/service status from deep-link or push data.
  • No need for +native-intent if return URL maps directly to Expo Router route.
  • Do not use broad mutate(() => true) or a global app-active refresh. Payment identifiers make targeted invalidation cheaper and deterministic.
  • Implement listeners and cache wiring under a separate mobile task, with tests for cold link, warm link, notification click, foreground notification, duplicate events, and pending-after-return.

Sources

  1. Post-assignment payment lifecycle, product API issue #111 - server authority, signed confirmation, one 20-minute PSE reconciliation, no polling, payload contract.
  2. Mobile app.json at researched revision - nursy application scheme.
  3. Expo Router: Customizing links - routing incoming links and native-only +native-intent limits.
  4. Mobile usePayments hook at researched revision and payment endpoint - current SWR key and per-hook mutate.
  5. Mobile root layout at researched revision - no SWR provider; repository search on 2026-08-05 found no SWRConfig, refreshInterval, revalidateOnFocus, or AppState configuration.
  6. SWR: global mutation - programmatic single-key and matcher revalidation.
  7. OneSignal React Native notifications API - click listener and paired removal API.
  8. Mobile useNotifications hook at researched revision - existing click-listener wrapper and subscription lifecycle.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment