Skip to content

Commit bcf8fef

Browse files
committed
feat: wip -> implement Culqi payment processing in Checkout component
1 parent a96ebce commit bcf8fef

File tree

1 file changed

+127
-84
lines changed

1 file changed

+127
-84
lines changed

src/routes/checkout/index.tsx

Lines changed: 127 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { zodResolver } from "@hookform/resolvers/zod";
22
import { X } from "lucide-react";
3-
import { useEffect, useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44
import { useForm } from "react-hook-form";
55
import { redirect, useNavigation, useSubmit } from "react-router";
66
import { z } from "zod";
@@ -22,6 +22,20 @@ import { commitSession, getSession } from "@/session.server";
2222

2323
import type { Route } from "./+types";
2424

25+
interface CulqiInstance {
26+
open: () => void;
27+
close: () => void;
28+
token?: { id: string };
29+
error?: Error;
30+
culqi?: () => void;
31+
}
32+
33+
declare global {
34+
interface Window {
35+
CulqiCheckout: new (publicKey: string, config: object) => CulqiInstance;
36+
}
37+
}
38+
2539
const countryOptions = [
2640
{ value: "AR", label: "Argentina" },
2741
{ value: "BO", label: "Bolivia" },
@@ -69,6 +83,30 @@ export async function action({ request }: Route.ActionArgs) {
6983
const cartItems = JSON.parse(
7084
formData.get("cartItemsJson") as string
7185
) as CartItem[];
86+
const token = formData.get("token") as string;
87+
88+
const body = {
89+
amount: 2000,
90+
currency_code: "PEN",
91+
email: shippingDetails.email,
92+
source_id: token,
93+
capture: true,
94+
};
95+
96+
const response = await fetch("https://api.culqi.com/v2/charges", {
97+
method: "POST",
98+
headers: {
99+
"content-type": "application/json",
100+
Authorization: `Bearer sk_test_EC8oOLd3ZiCTKqjN`,
101+
},
102+
body: JSON.stringify(body),
103+
});
104+
105+
if (!response.ok) {
106+
const errorData = await response.json();
107+
console.error("Error creating charge:", errorData);
108+
throw new Error("Error processing payment");
109+
}
72110

73111
const items = cartItems.map((item) => ({
74112
productId: item.product.id,
@@ -117,12 +155,14 @@ export default function Checkout({ loaderData }: Route.ComponentProps) {
117155
const navigation = useNavigation();
118156
const submit = useSubmit();
119157
const loading = navigation.state === "submitting";
120-
const [Culqui, setCulqui] = useState(null);
158+
const [culqui, setCulqui] = useState<CulqiInstance | null>(null);
159+
const scriptRef = useRef<HTMLScriptElement | null>(null);
121160

122161
const {
123162
register,
124163
handleSubmit,
125164
formState: { errors, isValid },
165+
getValues,
126166
} = useForm<CheckoutForm>({
127167
resolver: zodResolver(CheckoutFormSchema),
128168
defaultValues: {
@@ -141,93 +181,95 @@ export default function Checkout({ loaderData }: Route.ComponentProps) {
141181
});
142182

143183
useEffect(() => {
144-
const script = document.createElement("script");
145-
script.src = "https://js.culqi.com/checkout-js";
146-
script.async = true;
147-
script.onload = () => {
148-
// Aqui ya existe window.CulqiCheckout
149-
const settings = {
150-
title: "FullStock",
151-
currency: "USD",
152-
amount: total * 100, // Este parámetro es requerido para realizar pagos yape(80.00)
153-
// order: "ord_live_d1P0Tu1n7Od4nZdp", // Este parámetro es requerido para realizar pagos con pagoEfectivo, billeteras y Cuotéalo
154-
// xculqirsaid: "Inserta aquí el id de tu llave pública RSA",
155-
// rsapublickey: "Inserta aquí tu llave pública RSA",
156-
};
157-
158-
const paymentMethods = {
159-
// las opciones se ordenan según se configuren
160-
tarjeta: true,
161-
yape: false,
162-
billetera: false,
163-
bancaMovil: false,
164-
agente: false,
165-
cuotealo: false,
166-
};
167-
168-
const options = {
169-
lang: "auto",
170-
installments: true, // Habilitar o deshabilitar el campo de cuotas
171-
modal: true,
172-
// container: "#culqi-container", // Opcional - Div donde quieres cargar el checkout
173-
paymentMethods: paymentMethods,
174-
paymentMethodsSort: Object.keys(paymentMethods), // las opciones se ordenan según se configuren en paymentMethods
175-
};
176-
177-
const appearance = {
178-
theme: "default",
179-
hiddenCulqiLogo: false,
180-
hiddenBannerContent: false,
181-
hiddenBanner: false,
182-
hiddenToolBarAmount: false,
183-
hiddenEmail: false,
184-
menuType: "select", // sidebar / sliderTop / select
185-
buttonCardPayText: "Pagar", //
186-
logo: null, // 'http://www.childrensociety.ms/wp-content/uploads/2019/11/MCS-Logo-2019-no-text.jpg',
187-
defaultStyle: {
188-
bannerColor: "blue", // hexadecimal
189-
buttonBackground: "yellow", // hexadecimal
190-
menuColor: "pink", // hexadecimal
191-
linksColor: "green", // hexadecimal
192-
buttonTextColor: "blue", // hexadecimal
193-
priceColor: "red",
194-
},
195-
};
196-
197-
const config = {
198-
settings,
199-
// client,
200-
options,
201-
appearance,
202-
};
203-
204-
const publicKey = "pk_test_Ws4NXfH95QXlZgaz";
205-
// @ts-ignore
206-
const Culqi = new window.CulqiCheckout(publicKey, config);
207-
208-
setCulqui(Culqi);
209-
210-
console.log("Script loaded");
184+
// Function to load the Culqi script
185+
const loadCulqiScript = (): Promise<Window["CulqiCheckout"]> => {
186+
return new Promise<Window["CulqiCheckout"]>((resolve, reject) => {
187+
if (window.CulqiCheckout) {
188+
resolve(window.CulqiCheckout);
189+
return;
190+
}
191+
192+
// Create script element
193+
const script = document.createElement("script");
194+
script.src = "https://js.culqi.com/checkout-js";
195+
script.async = true;
196+
197+
// Store reference for cleanup
198+
scriptRef.current = script;
199+
200+
script.onload = () => {
201+
if (window.CulqiCheckout) {
202+
resolve(window.CulqiCheckout);
203+
} else {
204+
reject(
205+
new Error(
206+
"Culqi script loaded but CulqiCheckout object not found"
207+
)
208+
);
209+
}
210+
};
211+
212+
script.onerror = () => {
213+
reject(new Error("Failed to load CulqiCheckout script"));
214+
};
215+
216+
document.head.appendChild(script);
217+
});
211218
};
212219

213-
document.body.appendChild(script);
220+
loadCulqiScript()
221+
.then((CulqiCheckout) => {
222+
const config = {
223+
settings: {
224+
currency: "USD",
225+
amount: total * 100,
226+
},
227+
client: {
228+
email: user?.email,
229+
},
230+
options: {},
231+
appearance: {},
232+
};
233+
234+
const publicKey = "pk_test_Ws4NXfH95QXlZgaz";
235+
const culqiInstance = new CulqiCheckout(publicKey, config);
236+
237+
const handleCulqiAction = () => {
238+
if (culqiInstance.token) {
239+
const token = culqiInstance.token.id;
240+
culqiInstance.close();
241+
const formData = getValues();
242+
submit(
243+
{
244+
shippingDetailsJson: JSON.stringify(formData),
245+
cartItemsJson: JSON.stringify(cart.items),
246+
token,
247+
},
248+
{ method: "POST" }
249+
);
250+
} else {
251+
console.log("Error : ", culqiInstance.error);
252+
}
253+
};
254+
255+
culqiInstance.culqi = handleCulqiAction;
256+
257+
setCulqui(culqiInstance);
258+
})
259+
.catch((error) => {
260+
console.error("Error loading Culqi script:", error);
261+
});
214262

215-
// Cleanup function
216263
return () => {
217-
document.body.removeChild(script);
264+
if (scriptRef.current) {
265+
scriptRef.current.remove();
266+
}
218267
};
219-
}, []);
220-
221-
async function onSubmit(formData: CheckoutForm) {
222-
// submit(
223-
// {
224-
// shippingDetailsJson: JSON.stringify(formData),
225-
// cartItemsJson: JSON.stringify(cart.items),
226-
// },
227-
// { method: "POST" }
228-
// );
229-
if (Culqui) {
230-
Culqui.open();
268+
}, [total, user, submit, getValues, cart.items]);
269+
270+
async function onSubmit() {
271+
if (culqui) {
272+
culqui.open();
231273
}
232274
}
233275

@@ -353,6 +395,7 @@ export default function Checkout({ loaderData }: Route.ComponentProps) {
353395
</Button>
354396
</form>
355397
</div>
398+
<div id="culqi-container"></div>
356399
</Container>
357400
</Section>
358401
);

0 commit comments

Comments
 (0)