diff --git a/internal/v4/observations/parser.go b/internal/v4/observations/parser.go index f9d1975..a97da01 100644 --- a/internal/v4/observations/parser.go +++ b/internal/v4/observations/parser.go @@ -21,6 +21,7 @@ const ( AmazonPackage = "in.amazon.mShop.android.shopping" SuperMoneyPackage = "money.super.payments" Kotak811Package = "com.kotak811mobilebankingapp.instantsavingsupiscanandpayrecharge" + HDFCSmartHubPackage = "com.hdfc.smarthub" GenericNotificationSource = "android_notification" paytmPostTimeRefinementWindow = time.Minute ) @@ -28,7 +29,7 @@ const ( func isTrustedGenericPackage(packageName string) bool { switch packageName { case BHIMPackage, GooglePayBusinessPackage, GooglePayPackage, PaytmPackage, - PhonePePackage, AmazonPackage, SuperMoneyPackage, Kotak811Package: + PhonePePackage, AmazonPackage, SuperMoneyPackage, Kotak811Package, HDFCSmartHubPackage: return true default: return false diff --git a/internal/v4/observations/parser_test.go b/internal/v4/observations/parser_test.go index 7c62791..430c624 100644 --- a/internal/v4/observations/parser_test.go +++ b/internal/v4/observations/parser_test.go @@ -140,6 +140,7 @@ func TestParseGenericIncomingPaymentApplications(t *testing.T) { {"com.phonepe.app", "You received INR 250.41 from Maya via UPI", GenericNotificationSource}, {"com.phonepe.app", "You received INR 12.3 from Maya via UPI", GenericNotificationSource}, {"com.google.android.apps.nbu.paisa.user", "₹99.23 received from user@okaxis", GenericNotificationSource}, + {HDFCSmartHubPackage, "Payment received: ₹1.37 from Rahul via UPI", GenericNotificationSource}, } for _, tc := range cases { got, err := Parse(Snapshot{PackageName: tc.pkg, PostedAt: posted, Text: tc.text}) @@ -153,6 +154,28 @@ func TestParseGenericIncomingPaymentApplications(t *testing.T) { } } +func TestParseHDFCSmartHubIncomingNotification(t *testing.T) { + posted := time.Date(2026, 9, 16, 11, 12, 13, 456_000_000, time.FixedZone("IST", 5*60*60+30*60)) + got, err := Parse(Snapshot{ + PackageName: HDFCSmartHubPackage, + PostedAt: posted, + Title: "Payment received", + BigText: "You have received INR 300.47 from ARJUN K (arjun@okhdfcbank) via UPI.", + }) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + if got.Source != GenericNotificationSource || got.CollectionProfileID != "" || got.AmountPaise != 30047 { + t.Fatalf("observation = %+v", got) + } + if got.PayerName != "ARJUN K" || got.PayerUPIID != "arjun@okhdfcbank" { + t.Fatalf("payer = %q / %q", got.PayerName, got.PayerUPIID) + } + if !got.OccurredAt.Equal(posted.UTC()) || got.OccurredAtSource != "notification_posted_at" { + t.Fatalf("occurred = %s source=%s", got.OccurredAt, got.OccurredAtSource) + } +} + func TestParseGenericBHIMIncomingNotifications(t *testing.T) { posted := time.Date(2026, 9, 4, 10, 11, 12, 345_000_000, time.FixedZone("IST", 5*60*60+30*60)) cases := []struct { diff --git a/web-v4/src/PaymentsPage.tsx b/web-v4/src/PaymentsPage.tsx index 4efdc15..8e68066 100644 --- a/web-v4/src/PaymentsPage.tsx +++ b/web-v4/src/PaymentsPage.tsx @@ -212,6 +212,7 @@ function paymentAppLabel(packageName?: string): string { case "in.amazon.mShop.android.shopping": return "Amazon Pay"; case "money.super.payments": return "super.money"; case "com.kotak811mobilebankingapp.instantsavingsupiscanandpayrecharge": return "Kotak"; + case "com.hdfc.smarthub": return "HDFC SmartHub Vyapar"; default: return packageName ? "Payment app" : "—"; } }