import React, { useEffect, useState, useMemo } from "react"; import { View, ScrollView, Pressable, TextInput, StyleSheet, ActivityIndicator, Platform, PermissionsAndroid, Switch, } from "react-native"; import { useColorScheme } from "nativewind"; import { Text } from "@/components/ui/text"; import { ChevronDown, CalendarSearch, Search, Link2 } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { useSirouRouter } from "@sirou/react-native"; import { AppRoutes } from "@/lib/routes"; import { api } from "@/lib/api"; import { toast } from "@/lib/toast-store"; import { EmptyState } from "@/components/EmptyState"; import { PickerModal, SelectOption } from "@/components/PickerModal"; import { CalendarGrid } from "@/components/CalendarGrid"; import { FormFlow } from "@/components/FormFlow"; import { getPlaceholderColor } from "@/lib/colors"; import { getScanData } from "@/lib/scan-cache"; let SmsAndroid: any = null; if (Platform.OS === "android") { try { const smsModule = require("react-native-get-sms-android"); SmsAndroid = smsModule.default || smsModule; } catch (e) { console.log("[CreatePayment] SMS module unavailable"); } } const S = StyleSheet.create({ input: { paddingHorizontal: 12, paddingVertical: 12, fontSize: 12, fontWeight: "500", borderRadius: 6, borderWidth: 1, textAlignVertical: "center", }, }); function useInputColors() { const { colorScheme } = useColorScheme(); const dark = colorScheme === "dark"; return { bg: dark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)", border: dark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)", text: dark ? "#f1f5f9" : "#0f172a", placeholder: "rgba(100,116,139,0.45)", }; } function Field({ label, value, onChangeText, placeholder, numeric = false, flex, multiline = false, }: { label: string; value: string; onChangeText: (v: string) => void; placeholder: string; numeric?: boolean; flex?: number; multiline?: boolean; }) { const c = useInputColors(); return ( {label} ); } function PickerField({ label, value, onPress, }: { label: string; value: string; onPress: () => void; }) { const c = useInputColors(); return ( {label} {value} ); } const CURRENCIES = ["ETB", "USD", "EUR", "GBP", "KES", "ZAR"]; const PAYMENT_METHODS = [ "Telebirr", "CBE", "Dashen", "DECSI", "Bank Transfer", "Cash", "Credit Card", "Other", ]; const FINANCIAL_INSTITUTIONS = ["CBE", "ABYSSINIA", "TELE", "DASHEN"]; const PROVIDER_ALIASES: Record = { cbe: "CBE", telebirr: "TELE", tele: "TELE", dashen: "DASHEN", abyssinia: "ABYSSINIA", }; function normalizeFinancialInstitution(input: string | undefined | null): string | null { if (!input) return null; const key = String(input).trim().toLowerCase(); return PROVIDER_ALIASES[key] ?? null; } function parseSmsMessage(body: string) { const text = body.toUpperCase(); let bank = ""; let ref = ""; let amount = ""; if (text.includes("CBE")) { bank = "CBE"; const dirMatch = body.match(/(debited|credited)\s+with\s+ETB([\d,.]+)/i); if (dirMatch) amount = dirMatch[2]; const refMatch = body.match(/id=(\w+)/i); if (refMatch) ref = refMatch[1]; } else if (text.includes("TELEBIRR")) { bank = "Telebirr"; const dirMatch = body.match(/(received|sent|paid)\s+ETB\s*([\d,.]+)/i); if (dirMatch) amount = dirMatch[2]; const refMatch = body.match(/transaction number\s+(\w+)/i); if (refMatch) ref = refMatch[1]; } return { bank, ref, amount }; } export default function CreatePaymentScreen() { const nav = useSirouRouter(); const [step, setStep] = useState(0); const [submitting, setSubmitting] = useState(false); const [scanRecordId, setScanRecordId] = useState(null); const [transactionId, setTransactionId] = useState(""); const [amount, setAmount] = useState(""); const [currency, setCurrency] = useState("ETB"); const [paymentMethod, setPaymentMethod] = useState("Telebirr"); const [financialInstitution, setFinancialInstitution] = useState("CBE"); const [isReferenceVerified, setIsReferenceVerified] = useState(false); const [paymentDate, setPaymentDate] = useState( new Date().toISOString().split("T")[0], ); const [notes, setNotes] = useState(""); const [selectedInvoice, setSelectedInvoice] = useState(null); const [customerId, setCustomerId] = useState(""); const [showCurrency, setShowCurrency] = useState(false); const [showPaymentMethod, setShowPaymentMethod] = useState(false); const [showFinancialInstitution, setShowFinancialInstitution] = useState(false); const [showPaymentDate, setShowPaymentDate] = useState(false); const [showInvoicePicker, setShowInvoicePicker] = useState(false); const [invoices, setInvoices] = useState([]); const [invoiceSearch, setInvoiceSearch] = useState(""); const [loadingInvoices, setLoadingInvoices] = useState(false); const { colorScheme } = useColorScheme(); const isDark = colorScheme === "dark"; const c = useInputColors(); useEffect(() => { if (Platform.OS !== "android" || !SmsAndroid) return; (async () => { try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.READ_SMS, ); if (granted !== PermissionsAndroid.RESULTS.GRANTED) return; const fiveMinsAgo = Date.now() - 5 * 60 * 1000; const filter = { box: "inbox", minDate: fiveMinsAgo, maxCount: 30, }; SmsAndroid.list( JSON.stringify(filter), () => {}, (_count: number, smsList: string) => { const messages = JSON.parse(smsList); const match = messages.find((m: any) => { const addr = (m.address || "").toUpperCase(); const body = (m.body || "").toUpperCase(); return ( addr === "127" || addr === "CBE" || body.includes("TELEBIRR") || body.includes("CBE") ); }); if (!match) return; const { bank, ref, amount: parsedAmount, } = parseSmsMessage(match.body); if (ref || parsedAmount) { setTransactionId(ref || match.body.slice(0, 20)); if (parsedAmount) setAmount(parsedAmount.replace(/,/g, "")); if (bank) setPaymentMethod(bank); } }, ); } catch { // silent } })(); }, []); useEffect(() => { const payload = getScanData(); if (!payload) return; if (payload.type !== "payment" || !payload.id) return; setScanRecordId(payload.id); const scanData = payload.data || {}; if (scanData.transactionId) setTransactionId(String(scanData.transactionId)); if (scanData.amount != null) setAmount(String(scanData.amount)); if (scanData.currency) setCurrency(scanData.currency); if (scanData.paymentMethod) setPaymentMethod(scanData.paymentMethod); if (scanData.provider) { const normalized = normalizeFinancialInstitution(scanData.provider); if (normalized) setFinancialInstitution(normalized); } if (scanData.paymentDate) { try { setPaymentDate( new Date(scanData.paymentDate).toISOString().split("T")[0], ); } catch (_) {} } if ( scanData.referenceNumber || scanData.merchantName || scanData.merchantId ) { const parts: string[] = []; if (scanData.referenceNumber) parts.push(`Ref: ${scanData.referenceNumber}`); if (scanData.merchantName) parts.push(`Merchant: ${scanData.merchantName}`); if (scanData.merchantId) parts.push(`Merchant ID: ${scanData.merchantId}`); setNotes((prev) => prev ? `${prev}\n${parts.join(" · ")}` : parts.join(" · "), ); } }, []); const openInvoicePicker = async () => { setLoadingInvoices(true); try { const response = await api.invoices.getAll({ query: { limit: 50 } }); const list = Array.isArray(response) ? response : (response as any).data || []; setInvoices(list); setInvoiceSearch(""); setShowInvoicePicker(true); } catch (err: any) { toast.error("Error", "Failed to fetch invoices."); } finally { setLoadingInvoices(false); } }; const filteredInvoices = useMemo(() => { if (!invoiceSearch) return invoices; const q = invoiceSearch.toLowerCase(); return invoices.filter( (inv) => (inv.invoiceNumber || "").toLowerCase().includes(q) || (inv.customerName || "").toLowerCase().includes(q), ); }, [invoices, invoiceSearch]); const STEPS = [ { key: "details", label: "Payment Details" }, { key: "schedule", label: "Schedule" }, { key: "method", label: "Method" }, { key: "summary", label: "Summary" }, ]; const handleNext = () => { if (step === 0) { if (!transactionId.trim()) { toast.error("Validation Error", "Transaction ID is required"); return; } if (!amount || parseFloat(amount) <= 0) { toast.error( "Validation Error", "Amount is required and must be greater than 0", ); return; } } setStep(step + 1); }; const handleSubmit = async () => { if (!transactionId.trim()) { toast.error("Validation Error", "Transaction ID is required"); return; } if (!amount || parseFloat(amount) <= 0) { toast.error( "Validation Error", "Amount is required and must be greater than 0", ); return; } setSubmitting(true); try { const payload = { transactionId: transactionId.trim(), amount: parseFloat(amount), currency, paymentDate: new Date(paymentDate).toISOString(), paymentMethod, financialInstitution, isReferenceVerified, notes: notes.trim() || undefined, ...(selectedInvoice?.id ? { invoiceId: selectedInvoice.id } : {}), ...(customerId ? { customerId } : {}), }; if (scanRecordId) { await api.payments.update({ params: { id: scanRecordId }, body: payload, }); toast.success("Success", "Payment updated successfully!"); } else { await api.payments.create({ body: payload }); toast.success("Success", "Payment created successfully!"); } nav.back(); } catch (error: any) { console.error("[CreatePayment] Error:", error); const msg = error?.response?.data?.message || error?.data?.message || error?.message || (scanRecordId ? "Failed to update payment" : "Failed to create payment"); toast.error("Error", msg); } finally { setSubmitting(false); } }; return ( setStep(step - 1)} onComplete={handleSubmit} loading={submitting} completeLabel={scanRecordId ? "Update Payment" : "Create Payment"} > {step === 0 && ( Payment Details setShowCurrency(true)} /> setShowPaymentDate(true)} /> )} {step === 1 && ( Link Invoice Link Invoice {loadingInvoices ? ( ) : ( <> {selectedInvoice ? `#${selectedInvoice.invoiceNumber || selectedInvoice.id} — ${selectedInvoice.customerName || ""}` : "Select an invoice (optional)"} )} )} {step === 2 && ( Method setShowPaymentMethod(true)} /> setShowFinancialInstitution(true)} /> Reference verified Transaction reference has been confirmed )} {step === 3 && ( Summary Transaction ID {transactionId} Amount {currency}{" "} {(parseFloat(amount) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })} Payment Method {paymentMethod} Financial Institution {financialInstitution} Reference Verified {isReferenceVerified ? "Yes" : "No"} Payment Date {paymentDate} {selectedInvoice?.customerName ? ( Customer {selectedInvoice?.customerName} ) : null} Linked Invoice {selectedInvoice ? `#${selectedInvoice.invoiceNumber || selectedInvoice.id}` : "None"} {notes ? ( Notes {notes} ) : null} Total Amount {currency}{" "} {(parseFloat(amount) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })} )} setShowCurrency(false)} title="Select Currency" > {CURRENCIES.map((curr) => ( { setCurrency(v); setShowCurrency(false); }} /> ))} setShowPaymentMethod(false)} title="Select Payment Method" > {PAYMENT_METHODS.map((method) => ( { setPaymentMethod(v); setShowPaymentMethod(false); }} /> ))} setShowFinancialInstitution(false)} title="Select Financial Institution" > {FINANCIAL_INSTITUTIONS.map((fi) => ( { setFinancialInstitution(v); setShowFinancialInstitution(false); }} /> ))} setShowPaymentDate(false)} title="Select Payment Date" > { setPaymentDate(v); setShowPaymentDate(false); }} /> setShowInvoicePicker(false)} title="Link Invoice (Optional)" > {loadingInvoices ? ( ) : ( { setSelectedInvoice(null); setCustomerId(""); setShowInvoicePicker(false); }} className="px-4 py-3 border-b border-border/40 flex-row items-center" > None — skip linking {filteredInvoices.length > 0 ? ( filteredInvoices.map((inv) => ( { setSelectedInvoice(inv); setCustomerId(inv.customerId || ""); setShowInvoicePicker(false); }} className="px-4 py-3 border-b border-border/40 flex-row items-center" > {inv.customerName || "Unknown"} #{inv.invoiceNumber || inv.id} · {inv.currency || "ETB"}{" "} {Number(inv.amount || 0).toLocaleString()} )) ) : ( )} )} ); }