I've been trying tirelessly to integrate a transparent Mercadopago (cardForm) into my frontend(react) but for some reason I can't make it work, I keep getting the following response:
mercadopagoError [MercadoPagoError]: payment_method_id attribute can't be null
at Request._callback (C:\Users\WEBACADEMY\Documents\GitHub\uniPro-Backend\node_modules\mercadopago\lib\request-manager.js:344:19)
at self.callback (C:\Users\WEBACADEMY\Documents\GitHub\uniPro-Backend\node_modules\request\request.js:185:22)
at Request.emit (node:events:513:28)
at Request.<anonymous> (C:\Users\WEBACADEMY\Documents\GitHub\uniPro-Backend\node_modules\request\request.js:1154:10)
at Request.emit (node:events:513:28)
at IncomingMessage.<anonymous> (C:\Users\WEBACADEMY\Documents\GitHub\uniPro-Backend\node_modules\request\request.js:1076:12)
at Object.onceWrapper (node:events:627:28)
at IncomingMessage.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1359:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
cause: [
{
code: 4035,
description: "payment_method_id attribute can't be null",
data: '10-06-2023T16:11:50UTC;474d4e2d-bfd4-4d60-b0fd-7835fa8a9053'
}
],
status: 400,
idempotency: 'e4e7e41e-d868-4747-90e1-d4b8ebbfa591'
even tho when we check the config.data:
{"issuer_id":"25","payment_method_id":"visa","amount":"100.5","transaction_amount":1000,"installments":1,"description":"product description","paymentMethod":"card","payer":{"email":"f...a@email.com","identification":{"type":"CPF","number":"12312312312"}}}
that being said, my backend payment function on controller is:
async function Pay(req, res) {
try {
const { body } = req;
const { payer, paymentMethod } = body;
console.log(body.transaction_amount);
if (!body.transaction_amount) {
throw new Error("Valor inválido");
}
const {
token,
payment_method_id,
transaction_amount,
description,
installments,
email,
} = req.body;
let paymentService;
if (paymentMethod === 'pix') {
paymentService = new PixService();
} else if (paymentMethod === 'cartao') {
paymentService = new CartaoService();
} else {
throw new Error('Metodo de pagamento invalido');
}
const { status, ...rest } = await paymentService.execute({
token,
payment_method_id,
transaction_amount,
description,
installments,
email
});
if (status !== 201) {
throw new Error('Falha de pagamento!');
}
const data = await Order.create(rest);
if (!data) {
throw new Error('Falha ao salvar no banco!');
}
res.status(200).json({ status: 200, body: data });
} catch (error) {
console.log(error);
res.status(500).json({ error: 'Internal server error' });
}
}
with this as service:
class MercadopagoService {
async execute({
transaction_amount,
token,
description,
installments,
payment_method_id,
email
}) {
mercadopago.configurations.setAccessToken(config.mercadopago.access_token);
return await mercadopago.payment
.save({
transaction_amount,
token,
description,
installments,
payment_method_id,
payer: { email }
})
.then(async (data) => {
const { status, response } = data;
const { id, transaction_amount, date_approved, card } = response;
const { first_six_digits, last_four_digits, cardholder } = card;
return {
status,
id,
transaction_amount,
date_approved,
first_six_digits,
last_four_digits,
display_name: cardholder.name
};
});
}
}
and my frontend handling all that bomb:
import React, { useEffect } from "react";
import "./PaymentForm.css";
import { loadMercadoPago } from "@mercadopago/sdk-js";
import { Button, Col, Container, Row } from "react-bootstrap";
import axios from "axios";
const PaymentForm = () => {
useEffect(() => {
const initializeMercadoPago = async () => {
await loadMercadoPago();
const mp = new window.MercadoPago(
"PUBLIC_TEST_KEY"
);
const cardForm = mp.cardForm({
amount: "100.5",
iframe: true,
form: {
id: "form-checkout",
cardNumber: {
id: "form-checkout__cardNumber",
placeholder: "Numero do cartão",
},
expirationDate: {
id: "form-checkout__expirationDate",
placeholder: "Data de validade no formato: MM/AA",
},
securityCode: {
id: "form-checkout__securityCode",
placeholder: "Código de segurança",
},
cardholderName: {
id: "form-checkout__cardholderName",
placeholder: "Nome do titular do cartão",
},
issuer: {
id: "form-checkout__issuer",
placeholder: "Banco",
},
installments: {
id: "form-checkout__installments",
placeholder: "Parcelas",
},
identificationType: {
id: "form-checkout__identificationType",
placeholder: "Tipo de documento",
},
identificationNumber: {
id: "form-checkout__identificationNumber",
placeholder: "Número do documento do titular",
},
cardholderEmail: {
id: "form-checkout__cardholderEmail",
placeholder: "Email do titular",
},
},
callbacks: {
onFormMounted: (error: any) => {
if (error) {
console.warn("Form Mounted handling error: ", error);
} else {
console.log("Form mounted");
}
},
onSubmit: async (event: { preventDefault: () => void }) => {
event.preventDefault();
const {
paymentMethodId: payment_method_id,
issuerId: issuer_id,
cardholderEmail: email,
amount,
installments,
identificationNumber,
identificationType,
} = cardForm.getCardFormData();
// mandar pro /bookticket
const dataFromStorage = sessionStorage.getItem("user");
let authToken = "";
console.log("user", dataFromStorage);
if (dataFromStorage) {
const parsedData = JSON.parse(dataFromStorage);
authToken = parsedData.token;
}
try {
const response = await axios.post(
"http://localhost:3003/admin/pay",
{
issuer_id: cardForm.issuerId,
payment_method_id: cardForm.paymentMethodId,
amount,
transaction_amount: 1000,
installments: Number(installments),
description: "Descrição do produto",
paymentMethod: "cartao",
payer: {
email,
identification: {
type: identificationType,
number: identificationNumber,
},
},
},
{
headers: {
"Content-Type": "application/json",
Access: process.env.AccessToken,
Authorization: authToken,
},
}
);
console.log("rogerio: ",
JSON.stringify({
issuer_id,
payment_method_id,
amount,
transaction_amount: 1000,
installments: Number(installments),
description: "Descrição do produto",
paymentMethod: "cartao",
payer: {
email,
identification: {
type: identificationType,
number: identificationNumber,
},
},
})
);
} catch (error) {
console.error("Erro ao fazer a requisição:", error);
}
},
onFetching: (resource: any) => {
console.log("Fetching resource: ", resource);
// Animate progress bar
const progressBar = document.querySelector(".progress-bar");
if (progressBar) {
progressBar.removeAttribute("value");
return () => {
progressBar.setAttribute("value", "0");
};
}
},
},
});
};
initializeMercadoPago();
}, []);
return (
<Container>
<div className="PaymentForm">
<form id="form-checkout">
<Row>
<Col>
<div
id="form-checkout__cardNumber"
className="container mpFormInput"
></div>
</Col>
<Col>
<div
id="form-checkout__expirationDate"
className="container mpFormInput"
></div>
</Col>
</Row>
<Row>
<div
id="form-checkout__securityCode"
className="container mpFormInput"
></div>
</Row>
<input
type="text"
id="form-checkout__cardholderName"
className="cardHolderName mpFormInput"
/>
<Row>
<select
id="form-checkout__issuer"
className="container mpFormInput"
></select>
</Row>
<Row>
<select
id="form-checkout__installments"
className="container mpFormInput"
></select>
</Row>
<Row>
<select
id="form-checkout__identificationType"
className="container mpFormInput"
></select>
</Row>
<Row>
<input
type="text"
id="form-checkout__identificationNumber"
className="container mpFormInput"
/>
</Row>
<Row>
<input
type="email"
id="form-checkout__cardholderEmail"
className="container mpFormInput"
/>
</Row>
<Button
type="submit"
id="form-checkout__submit"
className="container"
>
Pagar
</Button>
<progress value="0" className="progress-bar">
Carregando...
</progress>
</form>
</div>
</Container>
);
};
PaymentForm.propTypes = {};
PaymentForm.defaultProps = {};
export default PaymentForm;
My question is why the backend is showing this behavior even when payment_method_id is not null in config.data? I know I'm missing something and also know that I'm not that far for the concluinsion, but Mercadopago's doc is not good at all and as it seem I'm not the only complaining about it at this point. Thanks in advance
Already tried the react-sdk-mercadopago and still couldnt acomplish it. At this point I just want to fix that payment_method_id issue so the next one can pop