Skip to content
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

[ADD] pos_mercado_pago: integration of Mercado Pago payment terminal #154962

Open
wants to merge 1 commit into
base: 17.0
Choose a base branch
from

Conversation

papyDoctor
Copy link
Contributor

@papyDoctor papyDoctor commented Feb 22, 2024

This PR add the Mercado Pago "Smart Point" payment terminal dedicated to the LATAM (Latin America) region

task-3350386

mool


I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr

@robodoo
Copy link
Contributor

robodoo commented Feb 22, 2024

@C3POdoo C3POdoo added the RD research & development, internal work label Feb 22, 2024
@C3POdoo C3POdoo requested review from a team and caburj and removed request for a team February 22, 2024 07:04
@papyDoctor papyDoctor force-pushed the 17.0-mercado-pago-mool branch 4 times, most recently from e2d9c92 to b8294b6 Compare February 29, 2024 07:52
Copy link
Contributor

@davidmonnom davidmonnom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First check but LGTM 👍

Will see with security team for secu review.

BTW, be carefull because endpoint URL contains external input / not hardcoded input

addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_terminal.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/security/ir.model.access.csv Outdated Show resolved Hide resolved
addons/pos_mercado_pago/views/pos_payment_method_views.xml Outdated Show resolved Hide resolved
setup.py Outdated Show resolved Hide resolved
Copy link
Contributor

@Ysoroko Ysoroko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff!
Need to check together for some things as it looks to me like lots of things can be simplified

  1. It seems like we're saving a lot of potentially unnecessary information (although it does look useful to the user). Maybe we can find a way to reduce the number of fields in the model
  2. Same for some requests, although these seem pretty reduced
  3. Need to check how to simplify the weird JS loop in addons/pos_mercado_pago/static/src/app/payment_mercado_pago.js

Will discuss this together next week

addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_terminal.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_terminal.py Outdated Show resolved Hide resolved
@papyDoctor papyDoctor force-pushed the 17.0-mercado-pago-mool branch 13 times, most recently from 1a57907 to 5d57daf Compare March 8, 2024 06:56
@qle-odoo
Copy link
Contributor

qle-odoo commented Apr 9, 2024

@papyDoctor you can ping security team and explai nwhy your PR is safe.
Check all warning and explain why in your case it is ok and security can override the flag
https://runbot.odoo.com/runbot/build/61010219

@papyDoctor
Copy link
Contributor Author

@odoo/rd-security
This module address a new payment method for the POS: Mercado Pago
The module needs to deal with some security risks on two files:

  1. The file main.py receives a webhook from Mercado Pago. In order to verify the sender authenticity, Mercado Pago uses a timestamped signature that we have to verify using hmac and hashlib. Also, we need to use the _sendone() from the request lib to notify the frontend but no informations are transmitted.
  2. The file mercado_pago_pos_request.py needs the requests lib to deals with Mercado Pago api, the address of which is hardcoded.

Can you review the code on these files,
Thank you

addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/mercado_pago_pos_request.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/mercado_pago_pos_request.py Outdated Show resolved Hide resolved
@papyDoctor papyDoctor force-pushed the 17.0-mercado-pago-mool branch 3 times, most recently from 048edb6 to f2f776e Compare April 10, 2024 06:52
Copy link
Collaborator

@xmo-odoo xmo-odoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few notes.

addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/mercado_pago_pos_request.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/mercado_pago_pos_request.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
data = mercado_pago._call_mercado_pago("get", "/point/integration-api/devices", {})
if 'devices' in data:
# Search for a device id that contains the serial number entered by the user
found_device = next((device for device in data['devices'] if point_smart in device.get('id')), None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why point_smart in device.get('id')?

  • it'll raise an exception if device does not have an id
  • assuming id is not a list, is it normal that we are looking up devices by substring rather than exact serial number, and might thus get multiple matches?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If device exists, it always has an id
  • The exact serial number is not known by the user, only part of it
    (example: serial number written on the back of the terminal: 14941269XX, exact serial number: PAX_A910__SMARTPOS14941269XX)
    No more than one can exist unless user enter an incomplete SN, in that case we take the first in the returned list

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If device exists, it always has an id

Then why is the code using dict.get?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, replaced with device['id']

if self.mp_bearer_token:
point_smart = vals.get('mp_id_point_smart', self.mp_id_point_smart)
if not 'name' in vals:
vals['mp_id_point_smart'] = self._find_terminal(vals.get('mp_bearer_token', self.mp_bearer_token), point_smart)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if an mp_bearer_token is configured we update the mp_id_point_smart on every write, for some reason unless we're touching the name?

And self.mp_id_point_smart should already have been validated previously, why are we re-validating it on every write?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First question: Yes, but I figured out that this logic is better:
if any(elem in vals for elem in['mp_id_point_smart', 'mp_bearer_token']):

Second question: each time user changes one (or the two) of the the two fields above, we have to recheck with Mercado data:

  • user change the bearer_token: check if correct
  • user change the id_point_smart: check to get the correct complete one

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But since the entire thing is in an if self.mp_bearer_token: it ignors the case where we would be setting the bearer token no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make things clearer but also to avoid infinite loop in the write I've added a new field "mp_id_point_smart_complet" that contains the complete id retrieved from Mercado Pago. It is used in the code instead of "mp_id_point_smart". The field "mp_id_point_smart" (the one entered by the user) is used only for seeking the complete one:

   def write(self, vals):
        records = super().write(vals)

        if 'mp_id_point_smart' in vals or 'mp_bearer_token' in vals:
            self.mp_id_point_smart_complet = self._find_terminal(self.mp_bearer_token, self.mp_id_point_smart)

        return records

@papyDoctor papyDoctor force-pushed the 17.0-mercado-pago-mool branch 2 times, most recently from 899dd58 to 1fbba28 Compare April 10, 2024 11:59
@papyDoctor
Copy link
Contributor Author

@mart-e and/or @xmo-odoo
Can you recheck please, I've almost included all your remarks, I just left unresolved the ones that are still in discussions
Thank you

addons/pos_mercado_pago/models/pos_payment_method.py Outdated Show resolved Hide resolved
if self.mp_bearer_token:
point_smart = vals.get('mp_id_point_smart', self.mp_id_point_smart)
if not 'name' in vals:
vals['mp_id_point_smart'] = self._find_terminal(vals.get('mp_bearer_token', self.mp_bearer_token), point_smart)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But since the entire thing is in an if self.mp_bearer_token: it ignors the case where we would be setting the bearer token no?

data = mercado_pago._call_mercado_pago("get", "/point/integration-api/devices", {})
if 'devices' in data:
# Search for a device id that contains the serial number entered by the user
found_device = next((device for device in data['devices'] if point_smart in device.get('id')), None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If device exists, it always has an id

Then why is the code using dict.get?

addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
addons/pos_mercado_pago/controllers/main.py Outdated Show resolved Hide resolved
Comment on lines +24 to +27
response = requests.request(method, endpoint, headers=header, json=payload, timeout=REQUEST_TIMEOUT)
return response.json()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not unless you raise_for_exception, by default a non-200 response is just that, a response with a status code which is no in the 200 range.

@papyDoctor papyDoctor force-pushed the 17.0-mercado-pago-mool branch 3 times, most recently from bde5945 to 7427cd1 Compare April 15, 2024 12:57
@papyDoctor
Copy link
Contributor Author

@qle-odoo modifications finished

@papyDoctor
Copy link
Contributor Author

@qle-odoo recheck please

@papyDoctor
Copy link
Contributor Author

@mart-e
@xmo-odoo
Can you re-check please?
Thank you,

Comment on lines 61 to 62
ts = sig[0].split('=')[1]
v1 = sig[1].split('=')[1]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure the entries are guaranteed to be in that order and you don't need to verify the keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it's the case now but since it's not guaranteed by Mercado Pago, I'll use a regex (already imported now) verification:

        ts_m = re.search(r"ts=(\d+)", x_signature)
        v1_m = re.search(r"v1=([a-f0-9]+)", x_signature)
        ts = ts_m.group(1) if ts_m else None
        v1 = v1_m.group(1) if v1_m else None
        if not ts or not v1:
            _logger.debug('Webhook bad X-Signature, ts: %s, v1: %s', ts, v1)
            return http.Response(status=400)

Thank you

Comment on lines +24 to +27
response = requests.request(method, endpoint, headers=header, json=payload, timeout=REQUEST_TIMEOUT)
return response.json()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to have been updated or replied to?

Comment on lines +35 to +38
if not self.env.user.has_group('point_of_sale.group_pos_user'):
raise AccessError(_("Do not have access to fetch token from Mercado Pago"))

mercado_pago = MercadoPagoPosRequest(self.sudo().mp_bearer_token)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error doesn't seem to have much to do with the actual behaviour?

@mart-e
Copy link
Contributor

mart-e commented Apr 26, 2024

@robodoo override=ci/security
a few nitpicking comments from Xavier remaining but fine security wise.
Thanks for the updates

This PR add the Mercado Pago "Smart Point" terminal
dedicated to the LATAM (Latin America) region

task-3350386

mool
@papyDoctor
Copy link
Contributor Author

@mart-e
I did a change related to @xmo-odoo comment on the signature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RD research & development, internal work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants