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()}
))
) : (
)}
)}
);
}