import React, { useEffect, useState, useMemo } from "react"; import { View, ScrollView, Pressable, TextInput, StyleSheet, ActivityIndicator, Platform, PermissionsAndroid, } 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 { CustomerPicker } from "@/components/CustomerPicker"; import { getPlaceholderColor } from "@/lib/colors"; 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", "Other", ]; 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 [transactionId, setTransactionId] = useState(""); const [amount, setAmount] = useState(""); const [currency, setCurrency] = useState("ETB"); const [paymentMethod, setPaymentMethod] = useState("Telebirr"); const [paymentDate, setPaymentDate] = useState( new Date().toISOString().split("T")[0], ); const [notes, setNotes] = useState(""); const [selectedInvoice, setSelectedInvoice] = useState(null); const [customerName, setCustomerName] = useState(""); const [customerEmail, setCustomerEmail] = useState(""); const [customerPhone, setCustomerPhone] = useState(""); const [showCurrency, setShowCurrency] = useState(false); const [showPaymentMethod, setShowPaymentMethod] = 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 } })(); }, []); 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: "invoice", label: "Invoice" }, { key: "info", label: "Customer Info" }, { 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, notes, customerName: customerName.trim() || undefined, customerEmail: customerEmail.trim() || undefined, customerPhone: customerPhone.trim() ? `+251${customerPhone.trim()}` : undefined, ...(selectedInvoice?.id ? { invoiceId: selectedInvoice.id } : {}), }; 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 || "Failed to create payment"; toast.error("Error", msg); } finally { setSubmitting(false); } }; return ( setStep(step - 1)} onComplete={handleSubmit} loading={submitting} completeLabel="Create Payment" > {step === 0 && ( Payment Details setShowCurrency(true)} /> setShowPaymentMethod(true)} /> setShowPaymentDate(true)} /> )} {step === 1 && ( Invoice Link Invoice {loadingInvoices ? ( ) : ( <> {selectedInvoice ? `#${selectedInvoice.invoiceNumber || selectedInvoice.id} — ${selectedInvoice.customerName || ""}` : "Select an invoice (optional)"} )} )} {step === 2 && ( Customer Info Customer Name { setCustomerName(c.name); setCustomerEmail(c.email); setCustomerPhone(c.phone.replace("+251", "")); }} placeholder="Select or search for a customer" /> Phone +251 )} {step === 3 && ( Summary Transaction ID {transactionId} Payment Method {paymentMethod} Payment Date {paymentDate} 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); }} /> ))} setShowPaymentDate(false)} title="Select Payment Date" > { setPaymentDate(v); setShowPaymentDate(false); }} /> setShowInvoicePicker(false)} title="Link Invoice (Optional)" > {loadingInvoices ? ( ) : ( { setSelectedInvoice(null); 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); 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()} )) ) : ( )} )} ); }