PayPal: Migrate to Order v2 API and ISU authentication (#2493) (#2614)

Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
Raphael Michel
2022-05-30 15:44:22 +02:00
committed by GitHub
parent 2e0be8c801
commit 925b8334a9
38 changed files with 3475 additions and 186 deletions

View File

@@ -0,0 +1,66 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import jwt
from paypalcheckoutsdk.core import PayPalEnvironment as VendorPayPalEnvironment
class PayPalEnvironment(VendorPayPalEnvironment):
def __init__(self, client_id, client_secret, api_url, web_url, merchant_id, partner_id):
super(PayPalEnvironment, self).__init__(client_id, client_secret, api_url, web_url)
self.merchant_id = merchant_id
self.partner_id = partner_id
def authorization_assertation(self):
if self.merchant_id:
return jwt.encode(
payload={
'iss': self.client_id,
'payer_id': self.merchant_id
},
key=None,
algorithm=None,
)
return ""
class SandboxEnvironment(PayPalEnvironment):
def __init__(self, client_id, client_secret, merchant_id=None, partner_id=None):
super(SandboxEnvironment, self).__init__(
client_id,
client_secret,
PayPalEnvironment.SANDBOX_API_URL,
PayPalEnvironment.SANDBOX_WEB_URL,
merchant_id,
partner_id
)
class LiveEnvironment(PayPalEnvironment):
def __init__(self, client_id, client_secret, merchant_id, partner_id):
super(LiveEnvironment, self).__init__(
client_id,
client_secret,
PayPalEnvironment.LIVE_API_URL,
PayPalEnvironment.LIVE_WEB_URL,
merchant_id,
partner_id
)

View File

@@ -0,0 +1,70 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import hashlib
from django.core.cache import cache
from paypalcheckoutsdk.core import (
AccessToken, PayPalHttpClient as VendorPayPalHttpClient,
)
class PayPalHttpClient(VendorPayPalHttpClient):
def __call__(self, request):
# First we get all the items that make up the current credentials and create a hash to detect changes
checksum = hashlib.sha256(''.join([
self.environment.base_url, self.environment.client_id, self.environment.client_secret
]).encode()).hexdigest()
cache_key_hash = f'pretix_paypal_token_hash_{checksum}'
token_hash = cache.get(cache_key_hash)
if token_hash:
# First we set an optional access token
self._access_token = AccessToken(
access_token=token_hash['access_token'],
expires_in=token_hash['expires_in'],
token_type=token_hash['token_type'],
)
# This is not part of the constructor - so we need to set it after the fact.
self._access_token.created_at = token_hash['created_at']
# Only then we'll call the original __call__() method, as it will verify the validity of the tokens
# and request new ones if required.
super().__call__(request)
# At this point - if there were any changes in access-token, we should have them and can cache them again
if self._access_token and (not token_hash or token_hash['access_token'] != self._access_token.access_token):
expiration = self._access_token.expires_in - 60 # For good measure, we expire 60 seconds earlier
cache.set(cache_key_hash, {
'access_token': self._access_token.access_token,
'expires_in': self._access_token.expires_in,
'token_type': self._access_token.token_type,
'created_at': self._access_token.created_at
}, expiration)
# And now for some housekeeping.
if self.environment.merchant_id:
request.headers["PayPal-Auth-Assertion"] = self.environment.authorization_assertation()
if self.environment.partner_id:
request.headers["PayPal-Partner-Attribution-Id"] = self.environment.partner_id

View File

@@ -0,0 +1,38 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
class PartnerReferralCreateRequest:
"""
Creates a Partner Referral.
"""
def __init__(self):
self.verb = "POST"
self.path = "/v2/customer/partner-referrals?"
self.headers = {}
self.headers["Content-Type"] = "application/json"
self.body = None
def prefer(self, prefer):
self.headers["Prefer"] = str(prefer)
def request_body(self, order):
self.body = order
return self

View File

@@ -0,0 +1,43 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
try:
from urllib import quote # Python 2.X
except ImportError:
from urllib.parse import quote # Python 3+
class PartnersMerchantIntegrationsGetRequest:
"""
Retrieves the Merchant Account Status of a Partner Merchant Integration.
"""
def __init__(self, partner_merchant_id, seller_merchant_id):
self.verb = "GET"
self.path = "/v1/customer/partners/{partner_merchant_id}/merchant-integrations/{seller_merchant_id}".format(
partner_merchant_id=quote(str(partner_merchant_id)),
seller_merchant_id=quote(str(seller_merchant_id))
)
self.headers = {}
self.headers["Content-Type"] = "application/json"
self.body = None
def prefer(self, prefer):
self.headers["Prefer"] = str(prefer)