summaryrefslogtreecommitdiff
path: root/addons/payment_paypal/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/payment_paypal/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/payment_paypal/models')
-rw-r--r--addons/payment_paypal/models/__init__.py3
-rw-r--r--addons/payment_paypal/models/payment.py240
2 files changed, 243 insertions, 0 deletions
diff --git a/addons/payment_paypal/models/__init__.py b/addons/payment_paypal/models/__init__.py
new file mode 100644
index 00000000..ef125336
--- /dev/null
+++ b/addons/payment_paypal/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import payment
diff --git a/addons/payment_paypal/models/payment.py b/addons/payment_paypal/models/payment.py
new file mode 100644
index 00000000..7cd53a4b
--- /dev/null
+++ b/addons/payment_paypal/models/payment.py
@@ -0,0 +1,240 @@
+# coding: utf-8
+
+import json
+import logging
+
+import dateutil.parser
+import pytz
+from werkzeug import urls
+
+from odoo import api, fields, models, _
+from odoo.addons.payment.models.payment_acquirer import ValidationError
+from odoo.addons.payment_paypal.controllers.main import PaypalController
+from odoo.tools.float_utils import float_compare
+
+
+_logger = logging.getLogger(__name__)
+
+
+class AcquirerPaypal(models.Model):
+ _inherit = 'payment.acquirer'
+
+ provider = fields.Selection(selection_add=[
+ ('paypal', 'Paypal')
+ ], ondelete={'paypal': 'set default'})
+ paypal_email_account = fields.Char('Email', required_if_provider='paypal', groups='base.group_user')
+ paypal_seller_account = fields.Char(
+ 'Merchant Account ID', groups='base.group_user',
+ help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.')
+ paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification', groups='base.group_user')
+ paypal_pdt_token = fields.Char(string='PDT Identity Token', help='Payment Data Transfer allows you to receive notification of successful payments as they are made.', groups='base.group_user')
+ # Default paypal fees
+ fees_dom_fixed = fields.Float(default=0.35)
+ fees_dom_var = fields.Float(default=3.4)
+ fees_int_fixed = fields.Float(default=0.35)
+ fees_int_var = fields.Float(default=3.9)
+
+ def _get_feature_support(self):
+ """Get advanced feature support by provider.
+
+ Each provider should add its technical in the corresponding
+ key for the following features:
+ * fees: support payment fees computations
+ * authorize: support authorizing payment (separates
+ authorization and capture)
+ * tokenize: support saving payment data in a payment.tokenize
+ object
+ """
+ res = super(AcquirerPaypal, self)._get_feature_support()
+ res['fees'].append('paypal')
+ return res
+
+ @api.model
+ def _get_paypal_urls(self, environment):
+ """ Paypal URLS """
+ if environment == 'prod':
+ return {
+ 'paypal_form_url': 'https://www.paypal.com/cgi-bin/webscr',
+ 'paypal_rest_url': 'https://api.paypal.com/v1/oauth2/token',
+ }
+ else:
+ return {
+ 'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
+ 'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token',
+ }
+
+ def paypal_compute_fees(self, amount, currency_id, country_id):
+ """ Compute paypal fees.
+
+ :param float amount: the amount to pay
+ :param integer country_id: an ID of a res.country, or None. This is
+ the customer's country, to be compared to
+ the acquirer company country.
+ :return float fees: computed fees
+ """
+ if not self.fees_active:
+ return 0.0
+ country = self.env['res.country'].browse(country_id)
+ if country and self.company_id.sudo().country_id.id == country.id:
+ percentage = self.fees_dom_var
+ fixed = self.fees_dom_fixed
+ else:
+ percentage = self.fees_int_var
+ fixed = self.fees_int_fixed
+ fees = (percentage / 100.0 * amount + fixed) / (1 - percentage / 100.0)
+ return fees
+
+ def paypal_form_generate_values(self, values):
+ base_url = self.get_base_url()
+
+ paypal_tx_values = dict(values)
+ paypal_tx_values.update({
+ 'cmd': '_xclick',
+ 'business': self.paypal_email_account,
+ 'item_name': '%s: %s' % (self.company_id.name, values['reference']),
+ 'item_number': values['reference'],
+ 'amount': values['amount'],
+ 'currency_code': values['currency'] and values['currency'].name or '',
+ 'address1': values.get('partner_address'),
+ 'city': values.get('partner_city'),
+ 'country': values.get('partner_country') and values.get('partner_country').code or '',
+ 'state': values.get('partner_state') and (values.get('partner_state').code or values.get('partner_state').name) or '',
+ 'email': values.get('partner_email'),
+ 'zip_code': values.get('partner_zip'),
+ 'first_name': values.get('partner_first_name'),
+ 'last_name': values.get('partner_last_name'),
+ 'paypal_return': urls.url_join(base_url, PaypalController._return_url),
+ 'notify_url': urls.url_join(base_url, PaypalController._notify_url),
+ 'cancel_return': urls.url_join(base_url, PaypalController._cancel_url),
+ 'handling': '%.2f' % paypal_tx_values.pop('fees', 0.0) if self.fees_active else False,
+ 'custom': json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) if paypal_tx_values.get('return_url') else False,
+ })
+ return paypal_tx_values
+
+ def paypal_get_form_action_url(self):
+ self.ensure_one()
+ environment = 'prod' if self.state == 'enabled' else 'test'
+ return self._get_paypal_urls(environment)['paypal_form_url']
+
+
+class TxPaypal(models.Model):
+ _inherit = 'payment.transaction'
+
+ paypal_txn_type = fields.Char('Transaction type')
+
+ # --------------------------------------------------
+ # FORM RELATED METHODS
+ # --------------------------------------------------
+
+ @api.model
+ def _paypal_form_get_tx_from_data(self, data):
+ reference, txn_id = data.get('item_number'), data.get('txn_id')
+ if not reference or not txn_id:
+ error_msg = _('Paypal: received data with missing reference (%s) or txn_id (%s)') % (reference, txn_id)
+ _logger.info(error_msg)
+ raise ValidationError(error_msg)
+
+ # find tx -> @TDENOTE use txn_id ?
+ txs = self.env['payment.transaction'].search([('reference', '=', reference)])
+ if not txs or len(txs) > 1:
+ error_msg = 'Paypal: received data for reference %s' % (reference)
+ if not txs:
+ error_msg += '; no order found'
+ else:
+ error_msg += '; multiple order found'
+ _logger.info(error_msg)
+ raise ValidationError(error_msg)
+ return txs[0]
+
+ def _paypal_form_get_invalid_parameters(self, data):
+ invalid_parameters = []
+ _logger.info('Received a notification from Paypal with IPN version %s', data.get('notify_version'))
+ if data.get('test_ipn'):
+ _logger.warning(
+ 'Received a notification from Paypal using sandbox'
+ ),
+
+ # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
+ if self.acquirer_reference and data.get('txn_id') != self.acquirer_reference:
+ invalid_parameters.append(('txn_id', data.get('txn_id'), self.acquirer_reference))
+ # check what is buyed
+ if float_compare(float(data.get('mc_gross', '0.0')), (self.amount + self.fees), 2) != 0:
+ invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % (self.amount + self.fees))) # mc_gross is amount + fees
+ if data.get('mc_currency') != self.currency_id.name:
+ invalid_parameters.append(('mc_currency', data.get('mc_currency'), self.currency_id.name))
+ if 'handling_amount' in data and float_compare(float(data.get('handling_amount')), self.fees, 2) != 0:
+ invalid_parameters.append(('handling_amount', data.get('handling_amount'), self.fees))
+ # check buyer
+ if self.payment_token_id and data.get('payer_id') != self.payment_token_id.acquirer_ref:
+ invalid_parameters.append(('payer_id', data.get('payer_id'), self.payment_token_id.acquirer_ref))
+ # check seller
+ if data.get('receiver_id') and self.acquirer_id.paypal_seller_account and data['receiver_id'] != self.acquirer_id.paypal_seller_account:
+ invalid_parameters.append(('receiver_id', data.get('receiver_id'), self.acquirer_id.paypal_seller_account))
+ if not data.get('receiver_id') or not self.acquirer_id.paypal_seller_account:
+ # Check receiver_email only if receiver_id was not checked.
+ # In Paypal, this is possible to configure as receiver_email a different email than the business email (the login email)
+ # In Odoo, there is only one field for the Paypal email: the business email. This isn't possible to set a receiver_email
+ # different than the business email. Therefore, if you want such a configuration in your Paypal, you are then obliged to fill
+ # the Merchant ID in the Paypal payment acquirer in Odoo, so the check is performed on this variable instead of the receiver_email.
+ # At least one of the two checks must be done, to avoid fraudsters.
+ if data.get('receiver_email') and data.get('receiver_email') != self.acquirer_id.paypal_email_account:
+ invalid_parameters.append(('receiver_email', data.get('receiver_email'), self.acquirer_id.paypal_email_account))
+ if data.get('business') and data.get('business') != self.acquirer_id.paypal_email_account:
+ invalid_parameters.append(('business', data.get('business'), self.acquirer_id.paypal_email_account))
+
+ return invalid_parameters
+
+ def _paypal_form_validate(self, data):
+ status = data.get('payment_status')
+ former_tx_state = self.state
+ res = {
+ 'acquirer_reference': data.get('txn_id'),
+ 'paypal_txn_type': data.get('payment_type'),
+ }
+ if not self.acquirer_id.paypal_pdt_token and not self.acquirer_id.paypal_seller_account and status in ['Completed', 'Processed', 'Pending']:
+ template = self.env.ref('payment_paypal.mail_template_paypal_invite_user_to_configure', False)
+ if template:
+ render_template = template._render({
+ 'acquirer': self.acquirer_id,
+ }, engine='ir.qweb')
+ mail_body = self.env['mail.render.mixin']._replace_local_links(render_template)
+ mail_values = {
+ 'body_html': mail_body,
+ 'subject': _('Add your Paypal account to Odoo'),
+ 'email_to': self.acquirer_id.paypal_email_account,
+ 'email_from': self.acquirer_id.create_uid.email_formatted,
+ 'author_id': self.acquirer_id.create_uid.partner_id.id,
+ }
+ self.env['mail.mail'].sudo().create(mail_values).send()
+
+ if status in ['Completed', 'Processed']:
+ try:
+ # dateutil and pytz don't recognize abbreviations PDT/PST
+ tzinfos = {
+ 'PST': -8 * 3600,
+ 'PDT': -7 * 3600,
+ }
+ date = dateutil.parser.parse(data.get('payment_date'), tzinfos=tzinfos).astimezone(pytz.utc).replace(tzinfo=None)
+ except:
+ date = fields.Datetime.now()
+ res.update(date=date)
+ self._set_transaction_done()
+ if self.state == 'done' and self.state != former_tx_state:
+ _logger.info('Validated Paypal payment for tx %s: set as done' % (self.reference))
+ return self.write(res)
+ return True
+ elif status in ['Pending', 'Expired']:
+ res.update(state_message=data.get('pending_reason', ''))
+ self._set_transaction_pending()
+ if self.state == 'pending' and self.state != former_tx_state:
+ _logger.info('Received notification for Paypal payment %s: set as pending' % (self.reference))
+ return self.write(res)
+ return True
+ else:
+ error = 'Received unrecognized status for Paypal payment %s: %s, set as error' % (self.reference, status)
+ res.update(state_message=error)
+ self._set_transaction_cancel()
+ if self.state == 'cancel' and self.state != former_tx_state:
+ _logger.info(error)
+ return self.write(res)
+ return True