Skip to content

[Parked – do not merge] UPI Scan & Pay (not production-ready)#6

Draft
SuTechs wants to merge 4 commits into
mainfrom
parked/upi-scan-pay
Draft

[Parked – do not merge] UPI Scan & Pay (not production-ready)#6
SuTechs wants to merge 4 commits into
mainfrom
parked/upi-scan-pay

Conversation

@SuTechs

@SuTechs SuTechs commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Status: 🅿️ Parked / do-not-merge. This preserves the full UPI scan-and-pay feature for the future. It has been removed from main (reset to the pre-feature commit 77481bb) because device testing surfaced blockers that can't be solved from an unregistered app.

What the feature does

Scan any UPI QR → enter amount / category / note → launch a UPI app to pay the scanned payee → track the expense locally. (Scanning via mobile_scanner; launch + app picker via a vendored, themed upi_intent; expense tracking only — we never move money.)

Why it's parked — limitations found in testing

  1. App detection is unreliable across devices.

    • upi_pay (now discontinued) used a stale hardcoded list → only iMobile/ICICI showed, not GPay/PhonePe.
    • upi_intent uses the live Android resolver, but queryIntentActivities("upi://pay") returns empty on some devices (e.g. Infinix) even though the merged manifest has the correct broad <data scheme="upi"> query and explicit <package> entries. Apps are installed but not detected.
  2. The payment itself is blocked by UPI/NPCI rules — the real wall.

    • First payment to a new/unverified payee is capped (the ₹2,000 "new payee" limit), and on some QRs even a ₹1 payment won't complete.
    • We hand off a plain upi://pay?… intent without a verified merchant signature (mc/sign), which only a registered PSP/merchant can produce — so apps treat it as an unverified payment and/or reject third-party-initiated intents.
    • Not fixable from the app side without becoming an onboarded UPI merchant/PSP.
  3. iOS is effectively unsupported.

    • iOS has no system app-chooser and no shared upi:// handler; each app uses a private scheme (gpay://, phonepe://, …).
    • The vendored package detects apps by their schemes but then launches the generic upi://pay?…, which no iOS app handles → payment can't complete on iOS.
  4. Payment gateways (Razorpay etc.) don't fit this use case.

    • A gateway collects money into your own merchant account; it can't forward a user's payment to an arbitrary scanned merchant's VPA. Doing that would make the app a regulated money-transfer/aggregator (KYC, licensing, backend, settlement).

What's preserved here (for future pickup)

  • mobile_scanner scan screen with dimmed-cutout overlay, close + flash controls
  • UPI QR parser (lib/data/utils/upi_uri.dart)
  • Payment form UI (payee header, hero amount, chip note, category quick-picks + search sheet)
  • Vendored & theme-aware upi_intent in packages/upi_intent (themed picker; dead boilerplate stripped; publish_to: none)
  • "Did the payment go through?" confirmation dialog (fallback for unreliable status)
  • Android <queries> + camera permission, iOS camera + UPI scheme entries

To revisit in future

  • Evaluate becoming/using a PSP integration (changes the product — money routes through us), or
  • Ship as tracker-only: user pays in their own UPI app, then logs the expense (no in-app initiation), or
  • Make it Android-only, best-effort, with the new-payee limit accepted.

⚠️ Do not merge until the payment-completion path is viable.

🤖 Generated with Claude Code

SuTechs and others added 4 commits June 1, 2026 12:54
* feat: scan UPI QR to track expense and pay via UPI apps

Adds a scan-and-pay flow: from the home app bar, scan any UPI QR,
enter amount/category/optional note, save the expense locally first,
then hand off to an installed UPI app (GPay, PhonePe, Paytm, BHIM…)
to complete the actual payment. This is tracking-only — the app never
moves money and stores just amount + category (plus existing
note/date/type), no payee details and no payment-status tracking.

- upi_uri.dart: pure-Dart UPI QR parser (pa/pn/am), rejects non-UPI QRs
- upi_scan_screen.dart: mobile_scanner camera + permission fallback
- upi_payment_screen.dart: amount/category/note form, saves via
  ExpenseCommand then opens the app picker
- upi_app_picker_sheet.dart: lists installed UPI apps, launches the
  chosen one (only file importing upi_pay, for easy future swap)
- platform config: Android camera permission + upi query; iOS camera
  usage description + UPI app schemes
- home_app_bar.dart: QR-scan entry point

Deps: mobile_scanner ^7.2.0, upi_pay ^1.1.0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(upi): polish scanner + payment UX, save only on app tap

Scanner page:
- Full-bleed camera with a stable, centered dimmed-cutout overlay
  (custom painter with corner brackets) — fixes the box animating in
  from the top-left
- Transparent app bar with a close (X) button and a flash toggle that
  reflects real torch state via ValueListenableBuilder
- scanWindow constrains detection to the framed area

Payment page (redesign):
- Payee header: avatar + name (big) + UPI id (muted)
- Hero centered amount input
- Note rendered as a compact chip-style field
- Category quick-pick chips (most relevant) + "More" opening a new
  CategoryPickerSheet with search + "Most used" + full list
- Single "Select app to pay" button

Behavior change: the expense is now saved ONLY when a UPI app is
actually tapped (UpiAppPickerSheet.onBeforeLaunch), not on form submit.
The picker disables tiles while launching and returns success so the
screen pops with a confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(upi): white scanner app bar text/icons, drop note field icon

- Force the scanner app bar title and icons (close + flash) to white so
  they read clearly over the camera/overlay
- Remove the edit icon from the payment note chip

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(upi): replace widget-returning methods with widget classes

Per review feedback, extract all `Widget _foo()` helper methods into
proper Stateless/Painter widget classes:

- components/category_chip.dart: shared CategoryChip (was duplicated in
  the payment screen and the category sheet)
- components/payment_widgets.dart: PayeeHeader, AmountField, NoteField,
  CategorySection, PayBar — payment screen now only has build() + logic
- category_picker_sheet.dart: uses CategoryChip + a _SectionLabel widget,
  inlined the chip builders
- upi_app_picker_sheet.dart: extract _AppTile widget (enabled/onTap)

No behavior change. flutter analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(upi): restore QR scanning by removing scanWindow

The scanWindow added during UI polish restricted detection to the
cutout rect, which silently broke scanning on-device (coordinate-space
mismatch with BoxFit.cover — camera previews but never detects).

Detection now runs on the full frame again; the cutout overlay remains
as a purely visual guide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
upi_pay (discontinued) detected apps from a stale hardcoded signature
list, so newer GPay/PhonePe builds never appeared — only iMobile showed.
Switch to upi_intent, which detects apps via the live Android intent
resolver and ships the correct package-visibility <queries>.

- Vendor the package into packages/upi_intent (path dependency) so we
  can theme its built-in picker; dropped example/ and unit tests
- Theme the picker to our navy palette via Theme.of(context).colorScheme
  .primary (no app->package coupling)
- Removed the deprecated package= attribute from the vendored manifest
  (conflicts with the AGP 8 namespace)
- Payment flow now uses UpiIntent.pay() and saves the expense ONLY when
  the app reports status == success; failure/unknown/submitted -> not
  saved, with a snackbar prompting manual entry
- Deleted our old custom UpiAppPickerSheet (replaced by themed built-in)
- Broadened the app manifest upi query (removed host="pay")
- Bumped version to 1.0.1+2

Caveat (documented in code): UPI status is unreliable — GPay often
returns no status on success and iOS returns none at all, so strict
success-only saving can miss genuine payments.

Debug APK builds; flutter analyze clean (one pre-existing bloc info).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App:
- Add a themed PaymentConfirmDialog ("Did the payment go through?") shown
  whenever the UPI app does not report a clear success status. This avoids
  silently dropping genuine payments — GPay often returns no status on
  success and iOS returns none at all. Confirm -> save; decline -> skip.

Vendored package (packages/upi_intent) — aligned as a standalone, internal fork:
- Rewrote README to document it as a vendored/customized copy (why, what
  changed, usage, platform setup, MIT attribution)
- Deleted dead `flutter create plugin` boilerplate (root-level
  upi_intent_method_channel.dart / upi_intent_platform_interface.dart);
  real impl is in lib/src/platform/
- Removed screenshots; set publish_to: "none"; updated description + CHANGELOG

flutter analyze clean; debug APK builds.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant