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