Commit e66a4d54 by G

feat: Payment processing using Wave as the payment gateway with navigation on…

feat: Payment processing using Wave as the payment gateway with navigation on QrCode screen upon valid initialization. User is given the possibility to verify the state manually with a button.
parent 0ddf355b
...@@ -5,6 +5,8 @@ import type { ...@@ -5,6 +5,8 @@ import type {
OmInitializationPayload as OmTransactionInitializationPayload, OmInitializationPayload as OmTransactionInitializationPayload,
OmInitializationResponse as OmTransactionInitializationResponse, OmInitializationResponse as OmTransactionInitializationResponse,
PaymentType, PaymentType,
WaveInitializationPayload,
WaveTransactionInitilizationResponse,
} from "./types"; } from "./types";
export const getPaymentTypes = () => { export const getPaymentTypes = () => {
...@@ -44,3 +46,11 @@ export const omVerifyTransactionStateWithTimeout = async ( ...@@ -44,3 +46,11 @@ export const omVerifyTransactionStateWithTimeout = async (
await sleep(retryAfter); await sleep(retryAfter);
} }
}; };
export const waveInitializeTransaction = (payload: WaveInitializationPayload) => {
return axiosInstance.post<WaveTransactionInitilizationResponse>("/transactions/", payload);
};
export const waveGetTransactionStatus = (id: string) => {
return axiosInstance.get<WaveTransactionInitilizationResponse>(`/wave-session/${id}/`);
};
...@@ -65,3 +65,27 @@ export interface OmTransaction { ...@@ -65,3 +65,27 @@ export interface OmTransaction {
txnid?: string; txnid?: string;
}; };
} }
// WAVE
export type WaveInitializationPayload = {
type_paiement: number;
marchand: string;
service: string;
montant: number;
};
export interface WaveTransactionInitilizationResponse {
id: string;
amount: string;
checkout_status: string;
client_reference: unknown;
currenfy: string;
error_url: string;
last_payment_errror: unknown;
business_name: string;
payment_status: string;
succes_url: string;
wave_launch_url: string;
when_completed: unknown;
when_created: string;
when_expires: string;
}
import type { NativeStackScreenProps } from "@react-navigation/native-stack"; import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import type { PaymentType } from "@/features/pay/types"; import type { PaymentType, WaveTransactionInitilizationResponse } from "@/features/pay/types";
import type { IwaveStarterRespone } from "@/utils/requests/wavePayment";
export type IpaymentStackNavigator = { export type IpaymentStackNavigator = {
homePageWithPaymentOptions: undefined; homePageWithPaymentOptions: undefined;
...@@ -26,7 +25,7 @@ export type ImainStackNavigator = { ...@@ -26,7 +25,7 @@ export type ImainStackNavigator = {
appBottomTabsNavigator: undefined; appBottomTabsNavigator: undefined;
paymentResultScreen: undefined; paymentResultScreen: undefined;
waveQrCodePaymentScreen: { waveQrCodePaymentScreen: {
data: IwaveStarterRespone; data: WaveTransactionInitilizationResponse;
}; };
}; };
......
...@@ -10,7 +10,11 @@ import type { AxiosError } from "axios"; ...@@ -10,7 +10,11 @@ import type { AxiosError } from "axios";
import * as WebBrowser from "expo-web-browser"; import * as WebBrowser from "expo-web-browser";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { AppState, Text, View } from "react-native"; import { AppState, Text, View } from "react-native";
import { omInitializeTransaction, omVerifyTransactionStateWithTimeout } from "@/features/pay/api"; import {
omInitializeTransaction,
omVerifyTransactionStateWithTimeout,
waveInitializeTransaction,
} from "@/features/pay/api";
import PaymentType from "@/features/pay/components/PaymentType"; import PaymentType from "@/features/pay/components/PaymentType";
import type { PaymentStackScreenComponentProps } from "@/navigations/types"; import type { PaymentStackScreenComponentProps } from "@/navigations/types";
...@@ -39,6 +43,16 @@ const PaymentAmountInputScreen: PaymentStackScreenComponentProps<"paymentAmountI ...@@ -39,6 +43,16 @@ const PaymentAmountInputScreen: PaymentStackScreenComponentProps<"paymentAmountI
numero: "0707070707", numero: "0707070707",
}); });
WebBrowser.openBrowserAsync(res.data.payment_url); WebBrowser.openBrowserAsync(res.data.payment_url);
} else if (code === "WAVE") {
const res = await waveInitializeTransaction({
type_paiement: 2,
marchand: "1",
service: "1",
montant: amount,
});
navigation?.getParent()?.navigate("waveQrCodePaymentScreen", {
data: res,
});
} else { } else {
navigation.navigate("numberAndOtpForPaymentScreen", { navigation.navigate("numberAndOtpForPaymentScreen", {
paymentType: route.params.paymentType, paymentType: route.params.paymentType,
......
import Button from "@components/Button"; import { asp as g } from "@asp/asp";
import BackgroundWithBeasyIconAndWhiteContentArea from "@components/backgrounds/BackgroundWithBeasyIconAndWhiteContentArea"; import { BarnoinPayBackground } from "@components/BarnoinPayBackground";
import Box from "@components/bases/Box"; import BeasyLogoIcon from "@components/BeasyLogoIcon";
import Text from "@components/bases/Text"; import * as Button from "@components/ButtonNew";
import useWave from "@hooks/useWave"; import * as Modal from "@components/Modal";
import AntDesign from "@expo/vector-icons/AntDesign";
import { LOG } from "@logger"; import { LOG } from "@logger";
import { Dimensions } from "react-native"; import { useMutation } from "@tanstack/react-query";
// biome-ignore lint/style/useNamingConvention: <explanation> import type { AxiosError } from "axios";
import QRCode from "react-native-qrcode-svg"; import { useState } from "react";
import { Dimensions, ScrollView, Text, View } from "react-native";
import QrCode from "react-native-qrcode-svg";
import { waveGetTransactionStatus } from "@/features/pay/api";
import type { MainStackScreenComponentProps } from "@/navigations/types"; import type { MainStackScreenComponentProps } from "@/navigations/types";
const log = LOG.extend("WaveQrCodePaymentScreen"); const log = LOG.extend("WaveQrCodePaymentScreen");
...@@ -16,62 +20,108 @@ const WaveQrCodePaymentScreen: MainStackScreenComponentProps<"waveQrCodePaymentS ...@@ -16,62 +20,108 @@ const WaveQrCodePaymentScreen: MainStackScreenComponentProps<"waveQrCodePaymentS
navigation, navigation,
}) => { }) => {
log.verbose("WaveQrCodePaymentScreen"); log.verbose("WaveQrCodePaymentScreen");
const [error, setError] = useState("");
const data = route.params.data; const data = route.params.data;
const windowWidth = Dimensions.get("window").width; const windowWidth = Dimensions.get("window").width;
const { handlePaymentVerification } = useWave(); const qrSize = windowWidth * 0.7;
const [isSuccess, setIsSuccess] = useState(false);
const query = useMutation({
mutationKey: ["waveTransactionStatus", data.id],
mutationFn: () => waveGetTransactionStatus(data.id),
onError: (err: AxiosError) => {
setError(JSON.stringify(err.response?.data) || err.message);
},
onSuccess: (_data) => {
setIsSuccess(true);
},
});
return ( return (
<BackgroundWithBeasyIconAndWhiteContentArea goBack={true}> <BarnoinPayBackground style={[g.justify_between]}>
<Box <View style={[g.px_lg, g.flex_row, g.align_center, g.justify_between]}>
style={{ <BeasyLogoIcon />
height: "100%", <AntDesign
width: "100%", name="arrowleft"
}} size={24}
p={"m"} color="black"
alignItems={"center"} onPress={() => navigation.goBack()}
> />
<Text </View>
variant={"black"} <View
mb={"l"} style={[
fontSize={20} g.w_full,
textAlign={"center"} g.p_5xl,
fontWeight={"bold"} g.gap_xl,
{
backgroundColor: "white",
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
height: "80%",
},
]}
> >
Votre QR Code <Text style={[g.text_3xl, g.font_bold]}>Votre QR Code</Text>
</Text>
<Box <View
style={{ height: windowWidth - 80, aspectRatio: 1 }} style={[
backgroundColor={"primary"} g.w_full,
alignItems={"center"} g.align_center,
justifyContent={"center"} g.justify_center,
borderRadius={20} g.p_md,
borderWidth={2} g.rounded_lg,
borderColor={"secondary"} g.shadow_elevated,
shadowColor={"black"} { aspectRatio: 1, backgroundColor: "white" },
shadowOffset={{ width: 0, height: 10 }} ]}
shadowOpacity={0.5}
shadowRadius={13.16}
elevation={20}
mb={"l"}
> >
<QRCode <QrCode value={data.wave_launch_url} size={qrSize} />
value={data.wave_launch_url} </View>
size={windowWidth - 120}
// backgroundColor={theme.colors.secondary}
/>
</Box>
<Text>Veuillez scanner le QR Code pour terminer le paiement</Text> <Text>Veuillez scanner le QR Code pour terminer le paiement</Text>
<Box width={"100%"} mt={"x100"}> <Button.Container onPress={() => query.mutate()} isLoading={query.isPending}>
<Button <Button.Label>Verification</Button.Label>
variant={"full"} </Button.Container>
textVariants={"white"} </View>
label="Verification"
onPress={() => handlePaymentVerification(data.id)} <Modal.RnModal visible={!!error} style={{ backgroundColor: "rgba(0, 0, 0, 0.25)" }}>
/> <Modal.OuterView
</Box> style={[
</Box> g.p_md,
</BackgroundWithBeasyIconAndWhiteContentArea> g.shadow_elevated,
{ backgroundColor: "white", width: "80%", height: "40%" },
]}
>
<View style={[g.flex_1, g.gap_md]}>
<ScrollView style={[g.flex_1]}>
<Text>{error}</Text>
</ScrollView>
<Button.Container onPress={() => setError("")}>
<Button.Label>OK</Button.Label>
</Button.Container>
</View>
</Modal.OuterView>
</Modal.RnModal>
<Modal.RnModal visible={isSuccess} style={{ backgroundColor: "rgba(0, 0, 0, 0.25)" }}>
<Modal.OuterView
style={[
g.p_md,
g.shadow_elevated,
{ backgroundColor: "white", width: "80%", height: "40%" },
]}
>
<View style={[g.flex_1, g.gap_md]}>
<Text style={[g.text_center]}>Transaction réussie</Text>
<Button.Container
onPress={() => navigation.getParent()?.navigate("paymentResultScreen")}
>
<Button.Label>OK</Button.Label>
</Button.Container>
</View>
</Modal.OuterView>
</Modal.RnModal>
</BarnoinPayBackground>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment