ecpay_expo/app/(tabs)/scanner.tsx
2025-01-09 19:12:18 +05:30

305 lines
9.4 KiB
TypeScript

import React, { useState, useEffect, useRef } from "react";
import { Modal, View, Text, Button, TextInput, SafeAreaView, StatusBar, Platform, StyleSheet, AppState } from "react-native";
import { CameraView } from "expo-camera";
import { Stack, useNavigation } from "expo-router"; // Updated import for navigation
import { Overlay } from "./Overlay";
import { ethers } from "ethers";
import { CHAIN_ID, CONTRACT_ADDRESS, MerchantData, TOKEN_CONTRACT_ADDRESS } from "./shared";
import { useWalletConnectModal } from "@walletconnect/modal-react-native";
import { contractABI } from "./ABI/transfer_contract";
import { erc20ABI } from "./ABI/erc20_contract";
import { GoogleSignin, User } from "@react-native-google-signin/google-signin";
import { ThemedText } from "@/components/ThemedText";
export default function Home() {
const qrLock = useRef(false);
const appState = useRef(AppState.currentState);
const navigation = useNavigation(); // Hook for navigation
const [scannedAddress, setScannedAddress] = useState("0xE09865aaCd816729C19E4BeA4caFf247704De9fE");
const [merchantData, setMerchantData] = useState<MerchantData | null>(null);
const [dollarValue, setDollarValue] = useState("400");
const [firebaseUser, setFirebaseUser] = useState<User | null>();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const subscription = AppState.addEventListener("change", (nextAppState) => {
if (appState.current.match(/inactive|background/) && nextAppState === "active") {
qrLock.current = false;
}
appState.current = nextAppState;
});
const checkSignInStatus = async () => {
try {
const userInfo = await GoogleSignin.signInSilently();
if (userInfo != null) {
console.log("Setting firebase user silently");
setFirebaseUser(userInfo.data);
}
} catch (error) {
console.error('Failed to check Google sign-in status:', error);
} finally {
}
};
checkSignInStatus();
return () => {
subscription.remove();
};
}, []);
const { open, isConnected, address, provider } = useWalletConnectModal();
const isValidEthAddress = (address: string) => /^0x[a-fA-F0-9]{40}$/.test(address);
const transferEthRemtoe = async () => {
setIsLoading(true);
const response = await fetch(`http://vps.playpoolstudios.com:30117/sendTransaction?tokenId=${firebaseUser?.idToken}&receiver=${scannedAddress}&amount=${dollarValue}`);
console.log(await response.text());
setIsLoading(false);
navigation.goBack();
}
const transferEth = async () => {
if (!provider || !isConnected) {
console.error("Wallet not connected.");
if (firebaseUser != null) {
transferEthRemtoe();
}
return;
}
try {
const ethersProvider = new ethers.providers.Web3Provider(provider);
const network = await ethersProvider.getNetwork();
if (network.chainId !== CHAIN_ID) {
console.log(`Switching from ${network.chainId} to ${CHAIN_ID} (Arbitrum Sepolia)...`);
await provider.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: `0x${CHAIN_ID.toString(16)}`, name: "Arbitrum Sepolia" }],
});
console.log(`Switched to chain ID: ${CHAIN_ID}`);
}
const priceResponse = await fetch("http://vps.playpoolstudios.com:28328/getMarkPriceUSDT");
const priceJson = await priceResponse.json();
const price = priceJson['price'];
// const ethValue = parseFloat(dollarValue) / price; // Convert dollars to ETH assuming 1 ETH = $3600
const usdtValue = parseFloat(dollarValue) / price; // ex: 10 USDT
console.log(`Converted ${dollarValue} THB to ${usdtValue} USDT`);
console.log("prepping the contract");
const signer = ethersProvider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, contractABI, signer);
const usdtTokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, erc20ABI, signer);
console.log("Requesting contract call");
const tx = await usdtTokenContract.approve(
CONTRACT_ADDRESS,
ethers.utils.parseUnits((usdtValue).toFixed(6), 18) // USDT has 6 decimals
);
await tx.wait();
console.log("USDT approved for transfer by the contract");
const tx2 = await contract.transferUSDT(scannedAddress, ethers.utils.parseUnits(usdtValue.toFixed(6), 6));
await tx2.wait();
console.log("Transaction sent:", tx.hash);
await tx.wait();
console.log("Transaction confirmed:", tx.hash);
// Navigate back after the transaction is confirmed
navigation.goBack();
} catch (error) {
if (error instanceof Error) {
console.error("Transfer failed:", error.message); // Logs the error message
// console.error("Stack trace:", error.stack); // Logs the full stack trace
} else {
console.error("Unexpected error:", error);
}
}
};
async function validateScannedQR(data: string) {
if (data === scannedAddress) {
// console.log(`${data} == ${scannedAddress}`);
qrLock.current = false;
return;
}
setScannedAddress(data);
const response = await fetch(`https://vps.playpoolstudios.com/ecpay/api/get_qr_by_id.php?id=${data}`);
const resText = await response.text();
qrLock.current = false;
if (resText == "Invalid ID") {
console.log("Invalid QR, DO NOTHING");
} else {
console.log(resText);
const resJson = JSON.parse(resText);
console.log(resJson);
setMerchantData(resJson);
}
}
useEffect(() => {
console.log("Merchant Data updated:", merchantData);
}, [merchantData]);
return (
<SafeAreaView style={StyleSheet.absoluteFillObject}>
<Stack.Screen
options={{
title: "Overview",
headerShown: false,
}}
/>
{Platform.OS === "android" ? <StatusBar hidden /> : null}
<CameraView
style={StyleSheet.absoluteFillObject}
facing="back"
onBarcodeScanned={({ data }) => {
if (data && !qrLock.current) {
qrLock.current = true;
validateScannedQR(data); // Save the scanned address
// setModalVisible(true); // Open the popup/modal
}
}}
/>
<Overlay />
{/* Loading Modal */}
{isLoading && (
<Modal animationType="fade" transparent={true} visible={isLoading}>
<View style={styles.loadingModalContainer}>
<View style={styles.loadingContent}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
</View>
</Modal>
)}
<Modal
animationType="slide"
transparent={true}
visible={merchantData !== null}//make this come up when merchant data is set
onRequestClose={() => { setMerchantData(null); setScannedAddress(""); }} // Close modal on back action
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Text style={styles.title}>Confirm Payment for {merchantData?.username}</Text>
{
merchantData?.price == "0" ?
<>
<Text style={styles.title}>Enter Amount</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="0.00"
keyboardType="numeric"
value={dollarValue}
onChangeText={setDollarValue}
/>
<Text style={styles.dollarSign}> THB</Text>
</View>
</>
:
<>
<Text style={styles.title}>Amount : {merchantData?.price} THB</Text>
</>
}
<Button title="Confirm payment" onPress={transferEth} color="#4CAF50" />
<View style={styles.spacer} />
<Button title="Close" onPress={() => { setMerchantData(null); setScannedAddress(""); }} color="#F44336" />
</View>
</View>
</Modal>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0,0,0,0.5)",
},
modalContent: {
width: "80%",
backgroundColor: "white",
borderRadius: 10,
padding: 20,
alignItems: "center",
elevation: 5,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
title: {
fontSize: 20,
fontWeight: "bold",
marginBottom: 10,
},
inputContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
},
dollarSign: {
fontSize: 18,
marginRight: 5,
color: "#333",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 5,
padding: 10,
width: "70%",
fontSize: 16,
textAlign: "left",
},
spacer: {
height: 10,
}, loadingModalContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0,0,0,0.5)",
},
loadingContent: {
width: "70%",
backgroundColor: "white",
padding: 20,
borderRadius: 10,
alignItems: "center",
elevation: 5,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
loadingText: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
},
});