1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import pprint
from odoo import _, fields, models
from odoo.exceptions import ValidationError
from odoo.addons.payment import utils as payment_utils
from odoo.addons.payment_paypal import utils as paypal_utils
from odoo.addons.payment_paypal.const import PAYMENT_STATUS_MAPPING
_logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
# See https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNandPDTVariables/
# this field has no use in Odoo except for debugging
paypal_type = fields.Char(string="PayPal Transaction Type")
def _get_specific_processing_values(self, processing_values):
""" Override of `payment` to return the Paypal-specific processing values.
Note: self.ensure_one() from `_get_processing_values`
:param dict processing_values: The generic and specific processing values of the
transaction.
:return: The dict of provider-specific processing values
:rtype: dict
"""
res = super()._get_specific_processing_values(processing_values)
if self.provider_code != 'paypal':
return res
partner_first_name, partner_last_name = payment_utils.split_partner_name(self.partner_name)
payload = {
'intent': 'CAPTURE',
'purchase_units': [
{
'reference_id': self.reference,
'description': f'{self.company_id.name}: {self.reference}',
'amount': {
'currency_code': self.currency_id.name,
'value': self.amount,
},
'payee': {
'display_data': {
'business_email': self.provider_id.company_id.email,
'brand_name': self.provider_id.company_id.name,
},
'email_address': paypal_utils.get_normalized_email_account(self.provider_id)
},
},
],
'payment_source': {
'paypal': {
'experience_context': {
'shipping_preference': 'NO_SHIPPING',
},
'email_address': self.partner_email,
'name': {
'given_name': partner_first_name,
'surname': partner_last_name,
},
'address': {
'address_line_1': self.partner_address,
'admin_area_1': self.partner_state_id.name,
'admin_area_2': self.partner_city,
'postal_code': self.partner_zip,
'country_code': self.partner_country_id.code,
},
},
},
}
_logger.info(
"Sending '/checkout/orders' request for transaction with reference %s:\n%s",
self.reference, pprint.pformat(payload)
)
order_data = self.provider_id._paypal_make_request(
'/v2/checkout/orders', json_payload=payload
)
_logger.info(
"Response of '/checkout/orders' request for transaction with reference %s:\n%s",
self.reference, pprint.pformat(order_data)
)
return {'order_id': order_data['id']}
def _get_tx_from_notification_data(self, provider_code, notification_data):
""" Override of `payment` to find the transaction based on Paypal data.
:param str provider_code: The code of the provider that handled the transaction.
:param dict notification_data: The notification data sent by the provider.
:return: The transaction if found.
:rtype: payment.transaction
:raise ValidationError: If the data match no transaction.
"""
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
if provider_code != 'paypal' or len(tx) == 1:
return tx
reference = notification_data.get('reference_id')
tx = self.search([('reference', '=', reference), ('provider_code', '=', 'paypal')])
if not tx:
raise ValidationError(
"PayPal: " + _("No transaction found matching reference %s.", reference)
)
return tx
def _process_notification_data(self, notification_data):
""" Override of `payment` to process the transaction based on Paypal data.
Note: self.ensure_one()
:param dict notification_data: The notification data sent by the provider.
:return: None
:raise ValidationError: If inconsistent data were received.
"""
super()._process_notification_data(notification_data)
if self.provider_code != 'paypal':
return
if not notification_data:
self._set_canceled(state_message=_("The customer left the payment page."))
return
amount = notification_data.get('amount').get('value')
currency_code = notification_data.get('amount').get('currency_code')
assert amount and currency_code, "PayPal: missing amount or currency"
assert self.currency_id.compare_amounts(float(amount), self.amount) == 0, \
"PayPal: mismatching amounts"
assert currency_code == self.currency_id.name, "PayPal: mismatching currency codes"
# Update the provider reference.
txn_id = notification_data.get('id')
txn_type = notification_data.get('txn_type')
if not all((txn_id, txn_type)):
raise ValidationError(
"PayPal: " + _(
"Missing value for txn_id (%(txn_id)s) or txn_type (%(txn_type)s).",
txn_id=txn_id, txn_type=txn_type
)
)
self.provider_reference = txn_id
self.paypal_type = txn_type
# Force PayPal as the payment method if it exists.
self.payment_method_id = self.env['payment.method'].search(
[('code', '=', 'paypal')], limit=1
) or self.payment_method_id
# Update the payment state.
payment_status = notification_data.get('status')
if payment_status in PAYMENT_STATUS_MAPPING['pending']:
self._set_pending(state_message=notification_data.get('pending_reason'))
elif payment_status in PAYMENT_STATUS_MAPPING['done']:
self._set_done()
elif payment_status in PAYMENT_STATUS_MAPPING['cancel']:
self._set_canceled()
else:
_logger.info(
"received data with invalid payment status (%s) for transaction with reference %s",
payment_status, self.reference
)
self._set_error(
"PayPal: " + _("Received data with invalid payment status: %s", payment_status)
)
|