Skip to content

Payments-refactor #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: payments
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/hooks/useCulqiScript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useRef, useState } from "react";

interface CulqiInstance {
open: () => void;
close: () => void;
token?: { id: string };
error?: Error;
culqi?: () => void;
}

declare global {
interface Window {
CulqiCheckout: new (publicKey: string, config: object) => CulqiInstance;
}
}

/**
* Custom hook to load Culqi Checkout script and return the CulqiCheckout constructor
* @returns {Window["CulqiCheckout"] | null} CulqiCheckout constructor or null if not loaded
*/
export function useCulqiScript(): Window["CulqiCheckout"] | null {
const [loaded, setLoaded] = useState(false);
const scriptRef = useRef<HTMLScriptElement | null>(null);

useEffect(() => {
if (window.CulqiCheckout) {
setLoaded(true);
return;
}
const script = document.createElement("script");
script.src = "https://js.culqi.com/checkout-js";
script.async = true;
scriptRef.current = script;
script.onload = () => setLoaded(true);
script.onerror = () => setLoaded(false);
document.head.appendChild(script);
return () => {
if (scriptRef.current) {
scriptRef.current.remove();
}
};
}, []);

return loaded ? window.CulqiCheckout ?? null : null;
}
3 changes: 1 addition & 2 deletions src/routes/cart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Form, Link } from "react-router";

import { Button, Container, Section } from "@/components/ui";
import { calculateTotal, getCart } from "@/lib/cart";
import { type Cart } from "@/models/cart.model";
import { getSession } from "@/session.server";

import type { Route } from "./+types";
Expand Down Expand Up @@ -55,7 +54,7 @@ export default function Cart({ loaderData }: Route.ComponentProps) {
</div>
<div className="flex flex-col gap-2 md:flex-row md:justify-between md:items-center">
<p className="text-sm font-medium">
${product.price.toFixed(2)}
S/{product.price.toFixed(2)}
</p>
<div className="flex gap-4 items-center">
<Form method="post" action="/cart/add-item">
Expand Down
140 changes: 48 additions & 92 deletions src/routes/checkout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCulqiScript } from "@/hooks/useCulqiScript";
import { zodResolver } from "@hookform/resolvers/zod";
import { X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { redirect, useNavigation, useSubmit } from "react-router";
import { z } from "zod";
Expand All @@ -10,8 +11,8 @@ import {
Container,
InputField,
Section,
Separator,
SelectField,
Separator,
} from "@/components/ui";
import { calculateTotal, getCart } from "@/lib/cart";
import { type CartItem } from "@/models/cart.model";
Expand Down Expand Up @@ -86,7 +87,7 @@ export async function action({ request }: Route.ActionArgs) {
const token = formData.get("token") as string;

const body = {
amount: 2000, // TODO: Calculate total dynamically
amount: Math.round(calculateTotal(cartItems) * 100), //TODO: Calculate total dynamically
currency_code: "PEN",
email: shippingDetails.email,
source_id: token,
Expand All @@ -97,7 +98,7 @@ export async function action({ request }: Route.ActionArgs) {
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer sk_test_EC8oOLd3ZiCTKqjN`, // TODO: Use environment variable
Authorization: `Bearer ${process.env.CULQUI_SECRET_KEY}`, // TODO: Use environment variable for security secret key
},
body: JSON.stringify(body),
});
Expand All @@ -109,6 +110,9 @@ export async function action({ request }: Route.ActionArgs) {
throw new Error("Error processing payment");
}

// Obtener el charge de Culqi
const charge = await response.json();

const items = cartItems.map((item) => ({
productId: item.product.id,
quantity: item.quantity,
Expand All @@ -119,7 +123,9 @@ export async function action({ request }: Route.ActionArgs) {

// TODO
// @ts-expect-error Arreglar el tipo de shippingDetails
const { id: orderId } = await createOrder(items, shippingDetails); // TODO: Add payment information to the order
const { id: orderId } = await createOrder(items, shippingDetails, {
culquiChargeId: charge.id,
}); // TODO: Add payment information to the order

await deleteRemoteCart(request);
const session = await getSession(request.headers.get("Cookie"));
Expand Down Expand Up @@ -158,7 +164,7 @@ export default function Checkout({ loaderData }: Route.ComponentProps) {
const loading = navigation.state === "submitting";

const [culqui, setCulqui] = useState<CulqiInstance | null>(null);
const scriptRef = useRef<HTMLScriptElement | null>(null);
const CulqiCheckout = useCulqiScript();

const {
register,
Expand All @@ -183,96 +189,46 @@ export default function Checkout({ loaderData }: Route.ComponentProps) {
});

useEffect(() => {
// Function to load the Culqi script
const loadCulqiScript = (): Promise<Window["CulqiCheckout"]> => {
return new Promise<Window["CulqiCheckout"]>((resolve, reject) => {
if (window.CulqiCheckout) {
resolve(window.CulqiCheckout);
return;
}

// Create script element
const script = document.createElement("script");
script.src = "https://js.culqi.com/checkout-js";
script.async = true;

// Store reference for cleanup
scriptRef.current = script;

script.onload = () => {
if (window.CulqiCheckout) {
resolve(window.CulqiCheckout);
} else {
reject(
new Error(
"Culqi script loaded but CulqiCheckout object not found"
)
);
}
};

script.onerror = () => {
reject(new Error("Failed to load CulqiCheckout script"));
};

document.head.appendChild(script);
});
if (!CulqiCheckout) return;
const config = {
settings: {
currency: "PEN",
amount: total * 100,
},
client: {
email: user?.email,
},
options: {
paymentMethods: {
tarjeta: true,
yape: false,
},
},
appearance: {},
};

loadCulqiScript()
.then((CulqiCheckout) => {
const config = {
settings: {
currency: "PEN",
amount: total * 100,
},
client: {
email: user?.email,
const publicKey = "pk_test_Ws4NXfH95QXlZgaz";
const culqiInstance = new CulqiCheckout(publicKey, config);
const handleCulqiAction = () => {
if (culqiInstance.token) {
const token = culqiInstance.token.id;
culqiInstance.close();
const formData = getValues();
submit(
{
shippingDetailsJson: JSON.stringify(formData),
cartItemsJson: JSON.stringify(cart.items),
token,
},
options: {
paymentMethods: {
tarjeta: true,
yape: false,
},
},
appearance: {},
};

const publicKey = "pk_test_Ws4NXfH95QXlZgaz";
const culqiInstance = new CulqiCheckout(publicKey, config);

const handleCulqiAction = () => {
if (culqiInstance.token) {
const token = culqiInstance.token.id;
culqiInstance.close();
const formData = getValues();
submit(
{
shippingDetailsJson: JSON.stringify(formData),
cartItemsJson: JSON.stringify(cart.items),
token,
},
{ method: "POST" }
);
} else {
console.log("Error : ", culqiInstance.error);
}
};

culqiInstance.culqi = handleCulqiAction;

setCulqui(culqiInstance);
})
.catch((error) => {
console.error("Error loading Culqi script:", error);
});

return () => {
if (scriptRef.current) {
scriptRef.current.remove();
{ method: "POST" }
);
} else {
console.log("Error : ", culqiInstance.error);
}
};
}, [total, user, submit, getValues, cart.items]);
culqiInstance.culqi = handleCulqiAction;
setCulqui(culqiInstance);
// Limpieza no necesaria, el hook ya remueve el script
}, [CulqiCheckout, total, user, submit, getValues, cart.items]);

async function onSubmit() {
if (culqui) {
Expand Down