Skip to content

Commit

Permalink
ft(booking):add FlutteWave payment method
Browse files Browse the repository at this point in the history
  • Loading branch information
23nosurrend committed Jul 17, 2024
1 parent 56ec67c commit f0f291b
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 6 deletions.
185 changes: 180 additions & 5 deletions app/(app)/ActionMenu/Booking/SelectPayment.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,183 @@
import { Colors } from "@/constants/Colors";
import { StatusBar } from "expo-status-bar";
import { useContext, useState } from "react";
import { ScrollView } from "react-native";
import { ScrollView,TouchableOpacity,View,Image } from "react-native";
import { Text } from "react-native";
import { ThemeContext } from "@/ctx/ThemeContext";
import Typography from "@/constants/Typography";
import Button from "@/components/UI/Button";
import { useEffect } from "react";
import { PaymentMethods } from "@/constants/PaymentMethods";
import PaymentChooseContainer from "@/components/UI/PaymentChooseContainer/Index";
import { router } from "expo-router";
import React from "react";
import { useLocalSearchParams } from "expo-router";

import { PayWithFlutterwave } from 'flutterwave-react-native'
import { supabase } from "@/lib/supabase";
import { useModal } from "@/ctx/ModalContext";
export default function SelectPayment() {
const { theme, changeTheme } = useContext(ThemeContext);
const [selected, setSelected] = useState(false);
const {doctor_id,hour,date,packageTitle,packagePrice,problem,user_id,patient_id,duration}=useLocalSearchParams()
const [loggedEmail,setLoggedEmail]=useState<string>("")
const { doctor_id, hour, date, packageTitle, packagePrice, problem, user_id, patient_id, duration } = useLocalSearchParams()

const modal = useModal()
const flutterKey=process.env.EXPO_PUBLIC_FLUTTERWAVE_KEY ?? ""

interface RedirectParams {
status: "successful" | "cancelled";
transaction_id?: string;
tx_ref: string;
}

useEffect(() => {
const fetchUser = async () => {

const { data: { user },error } = await supabase.auth.getUser()
if (error) {
console.error("error fetching user")
} else {
setLoggedEmail(user?.email||"logged Email")
}
}
fetchUser()

}, [loggedEmail])
async function bookAppointment() {
try {
const { error } = await supabase
.from('appointment')
.insert({
doctor_id: doctor_id,
time: hour, date: date,
package: packageTitle,
price: packagePrice,
illness_descr: problem,
user_id: patient_id,
duration: duration
});
} catch (error) {
console.log("Error while inserting data in booking ",error)
}

}
function successBooking() {
router.push("ActionMenu");;
modal.hide();
}
const showSuccefulModal = () => {
modal.show({
children: (
<View
style={{
padding: 40,
alignItems: "center",
gap: 20,
borderRadius: 48,
backgroundColor:
theme === "light" ? Colors.others.white : Colors.dark._2,
}}
>
<Image source={require("@/assets/images/calendarmodal.png")} />
<View
style={{
gap: 20,
backgroundColor:
theme === "light" ? Colors.others.white : Colors.dark._2,
}}
>
<Text
style={[
Typography.heading._4,
{
color: Colors.main.primary._500,
textAlign: "center",
},
]}
>
Congratulations!
</Text>
<Text
style={[
Typography.regular.large,
{
textAlign: "center",
color:
theme === "light"
? Colors.grayScale._900
: Colors.others.white,
},
]}
>
Appointment successfully booked. You will receive a notification
and the doctor you selected will contact you.
</Text>
<View
style={{
width: "100%",
backgroundColor: "red",
alignItems: "center",
justifyContent: "center",
}}
></View>
<Button
title="View Appointment"
onPress={successBooking}
/>
<TouchableOpacity
onPress={() => {
router.push("ActionMenu");
modal.hide();
}}
style={{
backgroundColor:
theme === "light" ? Colors.main.primary._100 : Colors.dark._3,
borderRadius: 100,
padding: 18,
alignItems: "center",
}}
>
<Text
style={[
Typography.bold.large,
{
color:
theme === "light"
? Colors.main.primary._500
: Colors.others.white,
},
]}
>
cancel
</Text>
</TouchableOpacity>
</View>
</View>
),
});
}
const handleOnRedirect = (data: RedirectParams) => {

console.log("redire data:", data)
if (data.status === "successful") {
bookAppointment()
showSuccefulModal()
} else {
alert("Payment Failed or cancelled ,please try again")
}
}
const generateRef = (length: number): string => {
const characters = flutterKey;
const charactersArray = characters.split('');
let result = '';

for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charactersArray.length);
result += charactersArray[randomIndex];
}

return result;
};

return (
<>
Expand All @@ -42,7 +204,20 @@ const {doctor_id,hour,date,packageTitle,packagePrice,problem,user_id,patient_id,
>
Select the payment method you want to use.
</Text>

<PayWithFlutterwave
onRedirect={handleOnRedirect}
options={{
tx_ref: generateRef(11),
authorization: 'FLWPUBK_TEST-3c390392d62e44fc5788cb0859823f05-X',
customer: {
email: loggedEmail
},
amount: 100,
currency: 'RWF',
payment_options: 'card'
}}
/>

<PaymentChooseContainer data={PaymentMethods} />

<Button
Expand All @@ -60,4 +235,4 @@ const {doctor_id,hour,date,packageTitle,packagePrice,problem,user_id,patient_id,
</ScrollView>
</>
);
}
}
106 changes: 106 additions & 0 deletions app/(app)/ActionMenu/Booking/SelectPaymentFlutter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { FlutterwaveInit } from 'flutterwave-react-native';

interface MyCartState {
isPending: boolean;
}

class MyCart extends React.Component<{}, MyCartState> {
abortController: AbortController | null = null;

constructor(props: {}) {
super(props);
this.state = {
isPending: false,
};
}

componentWillUnmount() {
if (this.abortController) {
this.abortController.abort();
}
}

handlePaymentInitialization = async () => {
this.setState(
{
isPending: true,
},
async () => {
// set abort controller
this.abortController = new AbortController();
try {
// initialize payment
const paymentLink = await FlutterwaveInit(
{
tx_ref: generateTransactionRef(),
authorization: '[merchant public key]',
amount: 100,
currency: 'USD',
customer: {
email: '[email protected]',
},
payment_options: 'card',
},
this.abortController.signal
);
// use payment link
this.usePaymentLink(paymentLink);
} catch (error: any) {
// do nothing if our payment initialization was aborted
if (error.code === 'ABORTERROR') {
return;
}
// handle other errors
this.displayErrorMessage(error.message);
}
}
);
};

generateTransactionRef = (): string => {
// Generate a transaction reference
return 'unique_transaction_ref';
};

usePaymentLink = (link: string) => {
// Use the payment link (navigate to a webview or open in browser)
console.log('Payment link:', link);
};

displayErrorMessage = (message: string) => {
// Display error message to the user
console.error('Error:', message);
};

render() {
const { isPending } = this.state;
return (
<View>
{/* Other components */}
<TouchableOpacity
style={[
styles.paymentButton,
isPending ? styles.paymentButtonBusy : {},
]}
disabled={isPending}
onPress={this.handlePaymentInitialization}
>
<Text>Pay $100</Text>
</TouchableOpacity>
</View>
);
}
}

const styles = StyleSheet.create({
paymentButton: {
// your button styling here
},
paymentButtonBusy: {
// your busy button styling here
},
});

export default MyCart;
4 changes: 4 additions & 0 deletions app/(app)/ActionMenu/Booking/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export default function BookingLayout() {
name="SelectPayment"
options={{ header: () => <Header title="Payments" /> }}
/>
{/* <Stack.Screen
name="SelectPaymentFlutter"
options={{ header: () => <Header title="Payments" /> }}
/> */}
</Stack>
</>
);
Expand Down
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f0f291b

Please sign in to comment.