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
base: 17.0
Are you sure you want to change the base?
Conversation
e2d9c92
to
b8294b6
Compare
b8294b6
to
501778c
Compare
501778c
to
efad3e8
Compare
There was a problem hiding this 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
There was a problem hiding this 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
- 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
- Same for some requests, although these seem pretty reduced
- 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
1a57907
to
5d57daf
Compare
3963a1d
to
2d7cfd9
Compare
@papyDoctor you can ping security team and explai nwhy your PR is safe. |
@odoo/rd-security
Can you review the code on these files, |
048edb6
to
f2f776e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few notes.
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) |
There was a problem hiding this comment.
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 anid
- 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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
899dd58
to
1fbba28
Compare
1fbba28
to
a9cd6a7
Compare
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
?
response = requests.request(method, endpoint, headers=header, json=payload, timeout=REQUEST_TIMEOUT) | ||
return response.json() |
There was a problem hiding this comment.
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.
bde5945
to
7427cd1
Compare
@qle-odoo modifications finished |
d449be6
to
8c69c03
Compare
@qle-odoo recheck please |
8c69c03
to
e080396
Compare
e080396
to
b8adabb
Compare
ts = sig[0].split('=')[1] | ||
v1 = sig[1].split('=')[1] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
response = requests.request(method, endpoint, headers=header, json=payload, timeout=REQUEST_TIMEOUT) | ||
return response.json() |
There was a problem hiding this comment.
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?
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) |
There was a problem hiding this comment.
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?
@robodoo override=ci/security |
This PR add the Mercado Pago "Smart Point" terminal dedicated to the LATAM (Latin America) region task-3350386 mool
b8adabb
to
f498461
Compare
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