diff --git a/.changeset/some-berries-teach.md b/.changeset/some-berries-teach.md new file mode 100644 index 00000000..b134ea36 --- /dev/null +++ b/.changeset/some-berries-teach.md @@ -0,0 +1,6 @@ +--- +"@godaddy/react": patch +"@godaddy/localizations": patch +--- + +Implementation of mercadopago diff --git a/examples/nextjs/app/checkout.tsx b/examples/nextjs/app/checkout.tsx index 0fe2ff77..9a381042 100644 --- a/examples/nextjs/app/checkout.tsx +++ b/examples/nextjs/app/checkout.tsx @@ -14,20 +14,52 @@ export function CheckoutPage({ session }: { session: CheckoutSession }) { ); } diff --git a/examples/nextjs/app/page.tsx b/examples/nextjs/app/page.tsx index 9ae69eb8..7593cdbf 100644 --- a/examples/nextjs/app/page.tsx +++ b/examples/nextjs/app/page.tsx @@ -58,17 +58,13 @@ export default async function Home() { processor: 'godaddy', checkoutTypes: ['standard'], }, - express: { - processor: 'godaddy', - checkoutTypes: ['express'], + mercadopago: { + processor: 'mercadopago', + checkoutTypes: ['standard'], }, paypal: { processor: 'paypal', - checkoutTypes: ['standard'], - }, - offline: { - processor: 'offline', - checkoutTypes: ['standard'], + checkoutTypes: ['express', 'standard'], }, }, operatingHours: { diff --git a/examples/nextjs/app/store/product/[productId]/product.tsx b/examples/nextjs/app/store/product/[productId]/product.tsx index 9aa51c71..9b2453e4 100644 --- a/examples/nextjs/app/store/product/[productId]/product.tsx +++ b/examples/nextjs/app/store/product/[productId]/product.tsx @@ -17,10 +17,7 @@ export default function Product({ productId }: { productId: string }) { Back to Store - + ); } diff --git a/examples/nextjs/biome.json b/examples/nextjs/biome.json index 808d736f..faa4f36f 100644 --- a/examples/nextjs/biome.json +++ b/examples/nextjs/biome.json @@ -1,20 +1,20 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.3/schema.json", - "extends": ["biome-config-godaddy/biome.json"], - "css": { - "parser": { - "cssModules": true, - "tailwindDirectives": true - } - }, - "files": { - "includes": ["**/*", "!!**/src/globals.css"] - }, - "linter": { - "rules": { - "correctness": { - "useUniqueElementIds": "off" - } - } - } + "$schema": "https://biomejs.dev/schemas/2.3.3/schema.json", + "extends": ["biome-config-godaddy/biome.json"], + "css": { + "parser": { + "cssModules": true, + "tailwindDirectives": true + } + }, + "files": { + "includes": ["**/*", "!!**/src/globals.css"] + }, + "linter": { + "rules": { + "correctness": { + "useUniqueElementIds": "off" + } + } + } } diff --git a/examples/nextjs/env.sample b/examples/nextjs/env.sample index 337ff345..abd13fa6 100644 --- a/examples/nextjs/env.sample +++ b/examples/nextjs/env.sample @@ -21,3 +21,7 @@ NEXT_PUBLIC_GODADDY_APP_ID= NEXT_PUBLIC_SQUARE_APP_ID= NEXT_PUBLIC_SQUARE_LOCATION_ID= NEXT_PUBLIC_PAYPAL_CLIENT_ID= + +# MercadoPago Credentials +NEXT_PUBLIC_MERCADOPAGO_PUBLIC_KEY= +NEXT_PUBLIC_MERCADOPAGO_COUNTRY=AR diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index a58bf364..a963947a 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -106,6 +106,7 @@ export const deDe = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Offline-Zahlungen', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const deDe = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Verwende das MercadoPago-Formular unten, um deinen Kauf sicher abzuschließen.', }, noMethodsAvailable: 'Keine Zahlungsmethoden verfügbar', cardNumber: 'Kartennummer', diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index 5751044b..14be3473 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -106,6 +106,7 @@ export const enIe = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Offline payments', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const enIe = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Use the MercadoPago form below to complete your purchase securely.', }, noMethodsAvailable: 'No payment methods available', cardNumber: 'Card number', diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index b9472309..023741f1 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -106,6 +106,7 @@ export const enUs = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Offline payments', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const enUs = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Use the MercadoPago form below to complete your purchase securely.', }, noMethodsAvailable: 'No payment methods available', cardNumber: 'Card number', diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index 92f66711..4d4d4ab7 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -107,6 +107,7 @@ export const esAr = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos en efectivo', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esAr = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index d1438c84..03add0ab 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -107,6 +107,7 @@ export const esCl = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos offline', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esCl = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index e82930d5..7dfb84ce 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -107,6 +107,7 @@ export const esCo = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos sin conexión', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esCo = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index 51a51eec..06af42fa 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -107,6 +107,7 @@ export const esEs = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos sin conexión', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esEs = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index 2a34cb82..569aea7d 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -107,6 +107,7 @@ export const esMx = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos fuera de línea', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esMx = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index 9dbeca8e..ba39de69 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -107,6 +107,7 @@ export const esPe = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos en efectivo', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esPe = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index ad231176..f6aa38b5 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -107,6 +107,7 @@ export const esUs = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagos offline', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const esUs = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa el formulario de MercadoPago a continuación para completar tu compra de forma segura.', }, noMethodsAvailable: 'No hay métodos de pago disponibles', cardNumber: 'Número de tarjeta', diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index 1e136e32..d4d36f82 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -107,6 +107,7 @@ export const frCa = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Paiements hors ligne', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const frCa = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Utilisez le formulaire MercadoPago ci-dessous pour finaliser votre achat en toute sécurité.', }, noMethodsAvailable: 'Aucune méthode de paiement disponible', cardNumber: 'Numéro de carte', diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index 04b2a67c..8525e8aa 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -107,6 +107,7 @@ export const frFr = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Paiements hors ligne', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const frFr = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Utilisez le formulaire MercadoPago ci-dessous pour finaliser votre achat en toute sécurité.', }, noMethodsAvailable: 'Aucune méthode de paiement disponible', cardNumber: 'Numéro de carte', diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index a0b2b8a6..673beba8 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -106,6 +106,7 @@ export const idId = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pembayaran offline', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const idId = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Gunakan formulir MercadoPago di bawah untuk menyelesaikan pembelian Anda dengan aman.', }, noMethodsAvailable: 'Tidak ada metode pembayaran tersedia', cardNumber: 'Nomor kartu', diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index e6d5a3d3..eab9b539 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -107,6 +107,7 @@ export const itIt = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagamenti offline', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const itIt = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Usa il modulo MercadoPago qui sotto per completare l’acquisto in modo sicuro.', }, noMethodsAvailable: 'Nessun metodo di pagamento disponibile', cardNumber: 'Numero della carta', diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index 40dac240..0e083448 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -106,6 +106,7 @@ export const ptBr = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Pagamentos offline', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const ptBr = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Use o formulário do MercadoPago abaixo para concluir sua compra com segurança.', }, noMethodsAvailable: 'Nenhum método de pagamento disponível', cardNumber: 'Número do cartão', diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index 2824a45a..a8db015e 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -107,6 +107,7 @@ export const qaPs = { googlePay: '[Göögië Þâÿ Þâÿmëñţ]', paze: '[Þâžë Þâÿmëñţ Šërvîçë]', offline: '[Öfflîñë þâÿmëñţ mëţhödš]', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -115,6 +116,8 @@ export const qaPs = { googlePay: '', paze: '', offline: '', + mercadopago: + '[Üšë ţhë MërçâðöÞâgö förm këlöw ţö çömþlëţë ÿöür þürçhâšë šëçürëlÿ.]', }, noMethodsAvailable: '[Ñö þâÿmëñţ mëţhödš âvâîlâblë âţ ţhîš ţîmë]', cardNumber: '[Çârd ñümkër îñþüţ fîëld]', diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index 72afea53..48de023b 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -106,6 +106,7 @@ export const trTr = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Çevrimdışı ödemeler', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const trTr = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Satın alımınızı güvenle tamamlamak için aşağıdaki MercadoPago formunu kullanın.', }, noMethodsAvailable: 'Kullanılabilir ödeme yöntemi yok', cardNumber: 'Kart numarası', diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index c1460e6c..72fdc757 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -106,6 +106,7 @@ export const viVn = { googlePay: 'Google Pay', paze: 'Paze', offline: 'Thanh toán ngoại tuyến', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -114,6 +115,8 @@ export const viVn = { googlePay: '', paze: '', offline: '', + mercadopago: + 'Hãy sử dụng biểu mẫu MercadoPago bên dưới để hoàn tất mua hàng một cách an toàn.', }, noMethodsAvailable: 'Không có phương thức thanh toán nào', cardNumber: 'Số thẻ', diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index e71929b9..b653202a 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -102,6 +102,7 @@ export const zhCn = { googlePay: 'Google Pay', paze: 'Paze', offline: '线下付款', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -110,6 +111,7 @@ export const zhCn = { googlePay: '', paze: '', offline: '', + mercadopago: '请使用下方的 MercadoPago 表单安全完成购买。', }, noMethodsAvailable: '暂无可用的付款方式', cardNumber: '卡号', diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index 286d58ff..aa8e2a81 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -102,6 +102,7 @@ export const zhSg = { googlePay: 'Google Pay', paze: 'Paze', offline: '线下付款', + mercadopago: 'Mercado Pago', }, descriptions: { creditCard: '', @@ -110,6 +111,7 @@ export const zhSg = { googlePay: '', paze: '', offline: '', + mercadopago: '请使用下方的 MercadoPago 表单安全完成购买。', }, noMethodsAvailable: '无可用付款方式', cardNumber: '卡号', diff --git a/packages/react/src/components/checkout/checkout.tsx b/packages/react/src/components/checkout/checkout.tsx index bc2a0b21..8ae642b4 100644 --- a/packages/react/src/components/checkout/checkout.tsx +++ b/packages/react/src/components/checkout/checkout.tsx @@ -80,6 +80,11 @@ export type PayPalConfig = { disableFunding?: Array<'credit' | 'card' | 'paylater' | 'venmo'>; }; +export type MercadoPagoConfig = { + publicKey: string; + country: 'AR' | 'BR' | 'CO' | 'CL' | 'PE' | 'MX'; +}; + interface CheckoutContextValue { elements?: CheckoutElements; targets?: Partial< @@ -92,6 +97,7 @@ interface CheckoutContextValue { godaddyPaymentsConfig?: GodaddyPaymentsConfig; squareConfig?: SquareConfig; paypalConfig?: PayPalConfig; + mercadoPagoConfig?: MercadoPagoConfig; isConfirmingCheckout: boolean; setIsConfirmingCheckout: (isConfirming: boolean) => void; checkoutErrors?: string[] | undefined; @@ -202,6 +208,7 @@ export interface CheckoutProps { godaddyPaymentsConfig?: GodaddyPaymentsConfig; squareConfig?: SquareConfig; paypalConfig?: PayPalConfig; + mercadoPagoConfig?: MercadoPagoConfig; layout?: LayoutSection[]; direction?: 'ltr' | 'rtl'; showStoreHours?: boolean; @@ -221,6 +228,7 @@ export function Checkout(props: CheckoutProps) { godaddyPaymentsConfig, squareConfig, paypalConfig, + mercadoPagoConfig, isCheckoutDisabled, } = props; @@ -385,6 +393,7 @@ export function Checkout(props: CheckoutProps) { stripeConfig, godaddyPaymentsConfig, squareConfig, + mercadoPagoConfig, paypalConfig, requiredFields, isConfirmingCheckout, diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/mercadopago/mercadopago.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/mercadopago/mercadopago.tsx new file mode 100644 index 00000000..29ea7781 --- /dev/null +++ b/packages/react/src/components/checkout/payment/checkout-buttons/mercadopago/mercadopago.tsx @@ -0,0 +1,226 @@ +import { LoaderCircle } from 'lucide-react'; +import React, { useCallback, useLayoutEffect, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useCheckoutContext } from '@/components/checkout/checkout'; +import { useDraftOrderTotals } from '@/components/checkout/order/use-draft-order'; +import { + PaymentProvider, + useConfirmCheckout, +} from '@/components/checkout/payment/utils/use-confirm-checkout'; +import { useIsPaymentDisabled } from '@/components/checkout/payment/utils/use-is-payment-disabled'; +import { useLoadMercadoPago } from '@/components/checkout/payment/utils/use-load-mercadopago'; +import { Button } from '@/components/ui/button'; +import { useGoDaddyContext } from '@/godaddy-provider'; +import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors'; +import { PaymentMethodType } from '@/types'; + +// Module-level singletons to prevent multiple SDK/brick instantiations +let mpInstance: any = null; +let bricksBuilderInstance: any = null; +let brickController: any = null; +let brickCreationPromise: Promise | null = null; +let isSubmitting = false; + +function getMercadoPagoInstance(publicKey: string) { + if (!mpInstance) { + mpInstance = new (window as any).MercadoPago(publicKey); + bricksBuilderInstance = mpInstance.bricks(); + } + return { mpInstance, bricksBuilderInstance }; +} + +export function MercadoPagoCheckoutButton() { + const { t } = useGoDaddyContext(); + const { mercadoPagoConfig, setCheckoutErrors, isConfirmingCheckout } = + useCheckoutContext(); + const isPaymentDisabled = useIsPaymentDisabled(); + const { data: totals } = useDraftOrderTotals(); + const form = useFormContext(); + const { isMercadoPagoLoaded } = useLoadMercadoPago(); + const confirmCheckout = useConfirmCheckout(); + + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isBrickReady, setIsBrickReady] = useState(!!brickController); + const elementId = 'mercadopago-brick-container'; + + const handleSubmit = useCallback( + async ({ formData }: any) => { + isSubmitting = true; + + const valid = await form.trigger(); + if (!valid) { + const firstError = Object.keys(form.formState.errors)[0]; + if (firstError) { + form.setFocus(firstError); + } + isSubmitting = false; + return; + } + + try { + const paymentToken = formData?.token; + + if (!paymentToken) { + throw new Error('No payment token received from MercadoPago'); + } + + await confirmCheckout.mutateAsync({ + paymentToken, + paymentType: PaymentMethodType.MERCADOPAGO, + paymentProvider: PaymentProvider.MERCADOPAGO, + }); + setError(''); + } catch (err: unknown) { + if (err instanceof GraphQLErrorWithCodes) { + setCheckoutErrors(err.codes); + } else { + setError(t.errors.errorProcessingPayment); + } + isSubmitting = false; + } + }, + [confirmCheckout, form, setCheckoutErrors, t.errors.errorProcessingPayment] + ); + + useLayoutEffect(() => { + const canInitialize = isMercadoPagoLoaded && mercadoPagoConfig?.publicKey; + + if (canInitialize) { + if (brickController) { + // Brick already exists, just mark as ready + setIsBrickReady(true); + } else if (brickCreationPromise) { + // Brick creation in progress, wait for it + brickCreationPromise.then(() => setIsBrickReady(true)); + } else { + // Create new brick + const renderBrick = async () => { + const total = totals?.total?.value || 0; + + try { + const container = document.getElementById(elementId); + if (container) { + container.innerHTML = ''; + } + + const { bricksBuilderInstance: bricksBuilder } = + getMercadoPagoInstance(mercadoPagoConfig.publicKey); + + brickController = await bricksBuilder.create('payment', elementId, { + initialization: { + amount: total, + payer: { email: 'dummy@testuser.com' }, + }, + customization: { + visual: { + hideFormTitle: true, + hidePaymentButton: true, + style: { theme: 'default' }, + }, + paymentMethods: { + creditCard: 'all', + debitCard: 'all', + maxInstallments: 1, + }, + }, + callbacks: { + onReady: () => { + setIsLoading(false); + const brickContainer = document.getElementById(elementId); + const formElement = brickContainer?.querySelector('form'); + if (formElement) { + formElement.style.padding = '0'; + const childDiv = formElement.querySelector(':scope > div'); + if (childDiv instanceof HTMLElement) { + childDiv.style.margin = '0'; + } + } + }, + onError: () => { + setError(t.errors.errorProcessingPayment); + setIsLoading(false); + }, + }, + }); + + setIsBrickReady(true); + } catch (_err) { + setError(t.errors.errorProcessingPayment); + setIsBrickReady(false); + brickCreationPromise = null; + } + }; + + brickCreationPromise = renderBrick(); + brickCreationPromise.finally(() => { + brickCreationPromise = null; + }); + } + } + + return () => { + // Don't unmount if submitting (parent replaces component with loading button) + // or if creation is in progress (React Strict Mode double-invocation) + if (brickController && !brickCreationPromise && !isSubmitting) { + try { + brickController.unmount(); + } catch (_e) { + // Ignore unmount errors + } + brickController = null; + } + }; + }, [ + isMercadoPagoLoaded, + mercadoPagoConfig?.publicKey, + elementId, + t.errors.errorProcessingPayment, + ]); + + const handleClick = async () => { + const valid = await form.trigger(); + if (!valid) { + const firstError = Object.keys(form.formState.errors)[0]; + if (firstError) { + form.setFocus(firstError); + } + return; + } + + if (brickController) { + const { formData } = await brickController.getFormData(); + await handleSubmit({ formData }); + } + }; + + return ( +
+
+ {error ? ( +

{error}

+ ) : null} + {!isConfirmingCheckout ? ( + + ) : ( + + )} +
+ ); +} diff --git a/packages/react/src/components/checkout/payment/icons/MercadoPago.tsx b/packages/react/src/components/checkout/payment/icons/MercadoPago.tsx new file mode 100644 index 00000000..a33064b1 --- /dev/null +++ b/packages/react/src/components/checkout/payment/icons/MercadoPago.tsx @@ -0,0 +1,39 @@ +export const MercadoPagoIcon = props => { + return ( + + + + + + + + + ); +}; + +export default MercadoPagoIcon; diff --git a/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx b/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx index 00bb6c53..f9ad0cda 100644 --- a/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx +++ b/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx @@ -70,7 +70,13 @@ const LazyComponents = { default: module.PayPalCreditCardCheckoutButton, })) ), - + MercadoPagoCheckoutButton: lazy(() => + import( + '@/components/checkout/payment/checkout-buttons/mercadopago/mercadopago' + ).then(module => ({ + default: module.MercadoPagoCheckoutButton, + })) + ), // Express Buttons ExpressCheckoutButton: lazy(() => import( @@ -179,6 +185,11 @@ type PaymentComponentRegistry = { button: PaymentComponentKey; }; }; + [PaymentMethodType.MERCADOPAGO]?: { + [PaymentProvider.MERCADOPAGO]: { + button: PaymentComponentKey; + }; + }; }; export const lazyPaymentComponentRegistry: PaymentComponentRegistry = { @@ -228,6 +239,11 @@ export const lazyPaymentComponentRegistry: PaymentComponentRegistry = { button: 'PazeCheckoutButton', }, }, + [PaymentMethodType.MERCADOPAGO]: { + [PaymentProvider.MERCADOPAGO]: { + button: 'MercadoPagoCheckoutButton', + }, + }, }; // Payment loading skeleton component diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index 3155d71e..adb607b7 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -18,6 +18,7 @@ import { } from '@/components/checkout/line-items'; import ApplePayIcon from '@/components/checkout/payment/icons/ApplePay'; import GooglePayIcon from '@/components/checkout/payment/icons/GooglePay'; +import MercadoPagoIcon from '@/components/checkout/payment/icons/MercadoPago'; import PayPalIcon from '@/components/checkout/payment/icons/PayPal'; import PazeIcon from '@/components/checkout/payment/icons/Paze'; import { @@ -66,6 +67,7 @@ const PAYMENT_METHOD_ICONS: Record = { applePay: , googlePay: , paze: , + mercadopago: , offline: , }; @@ -114,6 +116,8 @@ export function PaymentForm( return t.payment.methods.paze; case PaymentMethodType.OFFLINE: return t.payment.methods.offline; + case PaymentMethodType.MERCADOPAGO: + return t.payment.methods.mercadopago; default: return key; } @@ -137,6 +141,8 @@ export function PaymentForm( return t.payment.descriptions?.paze; case PaymentMethodType.OFFLINE: return t.payment.descriptions?.offline; + case PaymentMethodType.MERCADOPAGO: + return t.payment.descriptions?.mercadopago; default: return undefined; } @@ -269,7 +275,10 @@ export function PaymentForm( // Render the correct checkout button(s) for the selected payment method const getCheckoutButton = () => { - if (isConfirmingCheckout) { + if ( + isConfirmingCheckout && + paymentMethod !== PaymentMethodType.MERCADOPAGO + ) { return (