Skip to content
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
2 changes: 2 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn-error.log*
.pnpm-debug.log*

# local env files
.env
.env.local
.env.development.local
.env.test.local
Expand All @@ -39,3 +40,4 @@ yarn-error.log*

# analyze
/analyze
.yarn/*
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,21 @@ export default function Page() {
const verifyPayment = async () => {
setPaymentStatus("pending"); // Hide check status again
const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/payment/verify-new`)
.setUrl(`${address.backend}/api/payment/verify`)
.setHeaders({
"Content-Type": "application/json",
})
.setPayload(JSON.stringify({ id }))
.setPayload(JSON.stringify({ purchaseId: id }))
.build();

try {
setLoading(true);
const response = await fetch.exec();
if (response.status) {
setPaymentStatus(response.status);
setPaymentStatus(response.status === "success" ? "paid" : response.status);
}
} catch (error) {
console.error("Error verifying payment:", error);
} finally {
setLoading(false);
}
Expand Down
6 changes: 5 additions & 1 deletion apps/web/app/api/payment/verify-new/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface RequestPayload {
export async function POST(req: NextRequest) {
const body: RequestPayload = await req.json();
const domainName = req.headers.get("domain");
console.log("Domain:", domainName);
console.log("Body:", body);

try {
const domain = await getDomain(domainName);
Expand All @@ -34,8 +36,10 @@ export async function POST(req: NextRequest) {
if (!id) {
return Response.json({ message: "Bad request" }, { status: 400 });
}

console.log("Invoice ID:", id);
console.log("Invoices", await InvoiceModel.find());
const invoice = await InvoiceModel.findOne({ invoiceId: id });
console.log("Invoice:", invoice);

if (!invoice) {
return Response.json(
Expand Down
47 changes: 35 additions & 12 deletions apps/web/components/admin/dashboard/metric.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export const Metric = ({
query {
activities: getActivities(
type: ${type.toUpperCase()},
duration: _${internalDuration.toUpperCase()}
duration: _${internalDuration.toUpperCase()},
points: true
) {
count,
points {
Expand All @@ -77,24 +78,46 @@ export const Metric = ({
setLoading(true);
const response = await fetch.exec();
if (response.activities) {
const pointsWithDate = response.activities.points.map(
(point: { date: string; count: number }) => {
return {
date: new Date(
+point.date,
).toLocaleDateString(),
count: point.count,
};
},
);
const pointsWithDate =
response.activities.points?.map(
(point: {
date: string | number | Date;
count: number;
}) => {
console.log("Processing point:", point);
// Verificar el tipo de date y convertirlo apropiadamente
let dateObj: Date;

if (typeof point.date === "object") {
// Si es un objeto Date
dateObj = new Date(point.date.toString());
} else if (typeof point.date === "string") {
// Si es un string ISO
dateObj = new Date(point.date);
} else {
// Si es un timestamp numérico
dateObj = new Date(point.date);
}

return {
date: dateObj.toLocaleDateString(),
count: point.count,
};
},
) || [];

console.log(`Processed points:`, pointsWithDate);
console.log(`Total count:`, response.activities.count);

setData({
count: response.activities.count,
points: pointsWithDate,
});
} else {
console.log(`No activities data returned from API`);
}
} catch (err: any) {
console.log("Error in fetching activities"); // eslint-disable-line
console.error("Error in fetching activities:", err); // eslint-disable-line
} finally {
setLoading(false);
}
Expand Down
60 changes: 52 additions & 8 deletions apps/web/components/admin/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { Copy, Info } from "lucide-react";
import { Label } from "@components/ui/label";
import { Input } from "@components/ui/input";
import Resources from "@components/resources";
import { PAYMENT_METHOD_MERCADOPAGO } from "@courselit/common-models/dist/ui-constants";

const {
PAYMENT_METHOD_PAYPAL,
Expand Down Expand Up @@ -163,6 +164,7 @@ const Settings = (props: SettingsProps) => {
settings.lemonsqueezySubscriptionMonthlyVariantId,
lemonsqueezySubscriptionYearlyVariantId:
settings.lemonsqueezySubscriptionYearlyVariantId,
mercadopagoAccessToken: settings.mercadopagoAccessToken,
}),
);
}, [settings]);
Expand Down Expand Up @@ -195,7 +197,8 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
},
apikeys: getApikeys {
Expand Down Expand Up @@ -247,6 +250,7 @@ const Settings = (props: SettingsProps) => {
settingsResponse.lemonsqueezySubscriptionMonthlyVariantId || "",
lemonsqueezySubscriptionYearlyVariantId:
settingsResponse.lemonsqueezySubscriptionYearlyVariantId || "",
mercadopagoAccessToken: settingsResponse.mercadopagoAccessToken,
};
setSettings(
Object.assign({}, settings, settingsResponseWithNullsRemoved),
Expand Down Expand Up @@ -291,7 +295,8 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
}
}`;
Expand Down Expand Up @@ -358,7 +363,8 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
}
}`;
Expand Down Expand Up @@ -431,7 +437,8 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
}
}`;
Expand Down Expand Up @@ -496,7 +503,8 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
}
}`;
Expand Down Expand Up @@ -554,7 +562,8 @@ const Settings = (props: SettingsProps) => {
$lemonsqueezyWebhookSecret: String
$lemonsqueezyOneTimeVariantId: String,
$lemonsqueezySubscriptionMonthlyVariantId: String,
$lemonsqueezySubscriptionYearlyVariantId: String
$lemonsqueezySubscriptionYearlyVariantId: String,
$mercadopagoAccessToken: String
) {
settings: updatePaymentInfo(siteData: {
currencyISOCode: $currencyISOCode,
Expand All @@ -569,7 +578,8 @@ const Settings = (props: SettingsProps) => {
lemonsqueezyWebhookSecret: $lemonsqueezyWebhookSecret,
lemonsqueezyOneTimeVariantId: $lemonsqueezyOneTimeVariantId,
lemonsqueezySubscriptionMonthlyVariantId: $lemonsqueezySubscriptionMonthlyVariantId,
lemonsqueezySubscriptionYearlyVariantId: $lemonsqueezySubscriptionYearlyVariantId
lemonsqueezySubscriptionYearlyVariantId: $lemonsqueezySubscriptionYearlyVariantId,
mercadopagoAccessToken: $mercadopagoAccessToken
}) {
settings {
title,
Expand All @@ -595,11 +605,18 @@ const Settings = (props: SettingsProps) => {
codeInjectionHead,
codeInjectionBody,
mailingAddress,
hideCourseLitBranding
hideCourseLitBranding,
mercadopagoAccessToken
}
}
}`;

console.log("Mutation para guardar configuración:", query);
console.log(
"Token de Mercado Pago:",
newSettings.mercadopagoAccessToken,
);

try {
const fetchRequest = fetch
.setPayload({
Expand All @@ -623,6 +640,8 @@ const Settings = (props: SettingsProps) => {
newSettings.lemonsqueezySubscriptionMonthlyVariantId,
lemonsqueezySubscriptionYearlyVariantId:
newSettings.lemonsqueezySubscriptionYearlyVariantId,
mercadopagoAccessToken:
newSettings.mercadopagoAccessToken,
},
})
.build();
Expand Down Expand Up @@ -690,6 +709,9 @@ const Settings = (props: SettingsProps) => {
lemonsqueezySubscriptionYearlyVariantId: getNewSettings
? newSettings.lemonsqueezySubscriptionYearlyVariantId
: settings.lemonsqueezySubscriptionYearlyVariantId,
mercadopagoAccessToken: getNewSettings
? newSettings.mercadopagoAccessToken
: settings.mercadopagoAccessToken,
});

const removeApikey = async (keyId: string) => {
Expand Down Expand Up @@ -920,6 +942,12 @@ const Settings = (props: SettingsProps) => {
!x.lemonsqueezy,
),
},
{
label: capitalize(
PAYMENT_METHOD_MERCADOPAGO.toLowerCase(),
),
value: PAYMENT_METHOD_MERCADOPAGO,
},
]}
onChange={(value) =>
setNewSettings(
Expand Down Expand Up @@ -1042,6 +1070,22 @@ const Settings = (props: SettingsProps) => {
/>
</>
)}
{newSettings.paymentMethod ===
PAYMENT_METHOD_MERCADOPAGO && (
<>
<FormField
label={"Mercado Pago Access Token"}
name="mercadopagoAccessToken"
type="password"
value={
newSettings.mercadopagoAccessToken || ""
}
onChange={onChangeData}
sx={{ mb: 2 }}
autoComplete="off"
/>
</>
)}
{newSettings.paymentMethod ===
PAYMENT_METHOD_PAYPAL && (
<FormField
Expand Down
4 changes: 4 additions & 0 deletions apps/web/components/public/checkout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const Stripe = dynamic(() => import("./stripe"));
const Razorpay = dynamic(() => import("./razorpay"));
const Free = dynamic(() => import("./free"));
const Lemonsqueezy = dynamic(() => import("./lemonsqueezy"));
const MercadoPago = dynamic(() => import("./mercadopago"));

interface CheckoutExternalProps {
course: Course;
Expand Down Expand Up @@ -39,6 +40,9 @@ const CheckoutExternal = (props: CheckoutExternalProps) => {
{paymentMethod === UIConstants.PAYMENT_METHOD_PAYPAL && (
<></>
)}
{paymentMethod === UIConstants.PAYMENT_METHOD_MERCADOPAGO && (
<MercadoPago course={course} />
)}
</>
)}
</div>
Expand Down
84 changes: 84 additions & 0 deletions apps/web/components/public/checkout/mercadopago.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from "react";
import { Button, useToast } from "@courselit/components-library";
import {
ENROLL_BUTTON_TEXT,
TOAST_TITLE_ERROR,
} from "../../../ui-config/strings";
import { connect } from "react-redux";
import { useRouter } from "next/router";
import type { AppState, AppDispatch } from "@courselit/state-management";
import { Address, Course, SiteInfo } from "@courselit/common-models";
import { FetchBuilder } from "@courselit/utils";
import { actionCreators } from "@courselit/state-management";

const { networkAction } = actionCreators;

interface MercadoPagoProps {
course: Course;
siteInfo: SiteInfo;
address: Address;
dispatch: AppDispatch;
}

const MercadoPago = (props: MercadoPagoProps) => {
const { course, siteInfo, address, dispatch } = props;
const router = useRouter();
const { toast } = useToast();

const handleClick = async () => {
const payload = {
courseid: course.courseId,
metadata: JSON.stringify({
cancelUrl: `${address.frontend}${router.asPath}`,
successUrl: `${address.frontend}/checkout/${course.courseId}`,
sourceUrl: `/course/${course.slug}/${course.courseId}`,
}),
};
const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/payment/initiate`)
.setHeaders({
"Content-Type": "application/json",
})
.setPayload(JSON.stringify(payload))
.build();

try {
dispatch(networkAction(true));
const response = await fetch.exec({
redirectToOnUnAuth: router.asPath,
});
dispatch(networkAction(false));

if (response.status === "initiated") {
// If the API returns a payment URL, use it for redirection
if (response.paymentUrl) {
window.location.href = response.paymentUrl;
} else {
// Fallback to the old behavior
router.push(`/checkout/${course.courseId}?id=${response.paymentTracker}`);
}
} else if (response.status === "success") {
router.replace(`/course/${course.slug}/${course.courseId}`);
}
} catch (err: any) {
toast({
title: TOAST_TITLE_ERROR,
description: err.message,
variant: "destructive",
});
} finally {
dispatch(networkAction(false));
}
};

return <Button onClick={handleClick}>{ENROLL_BUTTON_TEXT}</Button>;
};

const mapStateToProps = (state: AppState) => ({
siteInfo: state.siteinfo,
address: state.address,
});

const mapDispatchToProps = (dispatch: AppDispatch) => ({ dispatch });

export default connect(mapStateToProps, mapDispatchToProps)(MercadoPago);
Loading