forked from CGM_Public/pretix_original
Revert "PayPal: Migrate to Order v2 API and ISU authentication (#2493)"
This reverts commit 9af1565db1.
This commit is contained in:
@@ -700,8 +700,8 @@ class Event(EventMixin, LoggedModel):
|
||||
|
||||
from ..signals import event_copy_data
|
||||
from . import (
|
||||
Discount, Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue,
|
||||
Question, Quota,
|
||||
Discount, Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, Question,
|
||||
Quota,
|
||||
)
|
||||
|
||||
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix import __version__ as version
|
||||
@@ -54,3 +55,12 @@ class PaypalApp(AppConfig):
|
||||
|
||||
def ready(self):
|
||||
from . import signals # NOQA
|
||||
|
||||
@cached_property
|
||||
def compatibility_errors(self):
|
||||
errs = []
|
||||
try:
|
||||
import paypalrestsdk # NOQA
|
||||
except ImportError:
|
||||
errs.append("Python package 'paypalrestsdk' is not installed.")
|
||||
return errs
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
)
|
||||
@@ -1,70 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -1,38 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -1,43 +0,0 @@
|
||||
#
|
||||
# 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)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,27 +24,18 @@ from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix import settings
|
||||
from pretix.base.forms import SecretKeySettingsField
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
from pretix.base.settings import settings_hierarkey
|
||||
from pretix.base.signals import (
|
||||
logentry_display, register_global_settings, register_payment_providers,
|
||||
)
|
||||
from pretix.plugins.paypal.payment import PaypalMethod
|
||||
from pretix.presale.signals import html_head, process_response
|
||||
|
||||
|
||||
@receiver(register_payment_providers, dispatch_uid="payment_paypal")
|
||||
def register_payment_provider(sender, **kwargs):
|
||||
from .payment import PaypalAPM, PaypalSettingsHolder, PaypalWallet
|
||||
return [PaypalSettingsHolder, PaypalWallet, PaypalAPM]
|
||||
from .payment import Paypal
|
||||
return Paypal
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="paypal_logentry_display")
|
||||
@@ -61,7 +52,6 @@ def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
'PAYMENT.SALE.REFUNDED': _('Payment refunded.'),
|
||||
'PAYMENT.SALE.REVERSED': _('Payment reversed.'),
|
||||
'PAYMENT.SALE.PENDING': _('Payment pending.'),
|
||||
'CHECKOUT.ORDER.APPROVED': _('Order approved.'),
|
||||
}
|
||||
|
||||
if event_type in plains:
|
||||
@@ -77,20 +67,15 @@ def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
def register_global_settings(sender, **kwargs):
|
||||
return OrderedDict([
|
||||
('payment_paypal_connect_client_id', forms.CharField(
|
||||
label=_('PayPal ISU/Connect: Client ID'),
|
||||
label=_('PayPal Connect: Client ID'),
|
||||
required=False,
|
||||
)),
|
||||
('payment_paypal_connect_secret_key', SecretKeySettingsField(
|
||||
label=_('PayPal ISU/Connect: Secret key'),
|
||||
required=False,
|
||||
)),
|
||||
('payment_paypal_connect_partner_merchant_id', forms.CharField(
|
||||
label=_('PayPal ISU/Connect: Partner Merchant ID'),
|
||||
help_text=_('This is not the BN-code, but rather the ID of the merchant account which holds branding information for ISU.'),
|
||||
label=_('PayPal Connect: Secret key'),
|
||||
required=False,
|
||||
)),
|
||||
('payment_paypal_connect_endpoint', forms.ChoiceField(
|
||||
label=_('PayPal ISU/Connect Endpoint'),
|
||||
label=_('PayPal Connect Endpoint'),
|
||||
initial='live',
|
||||
choices=(
|
||||
('live', 'Live'),
|
||||
@@ -98,73 +83,3 @@ def register_global_settings(sender, **kwargs):
|
||||
),
|
||||
)),
|
||||
])
|
||||
|
||||
|
||||
@receiver(html_head, dispatch_uid="payment_paypal_html_head")
|
||||
def html_head_presale(sender, request=None, **kwargs):
|
||||
provider = PaypalMethod(sender)
|
||||
url = resolve(request.path_info)
|
||||
|
||||
if provider.settings.get('_enabled', as_type=bool) and (
|
||||
url.url_name == "event.order.pay.change" or
|
||||
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
||||
(url.namespace == "plugins:paypal" and url.url_name == "pay")
|
||||
):
|
||||
provider.init_api()
|
||||
template = get_template('pretixplugins/paypal/presale_head.html')
|
||||
|
||||
ctx = {
|
||||
'client_id': provider.client.environment.client_id,
|
||||
'merchant_id': provider.client.environment.merchant_id,
|
||||
'csp_nonce': _nonce(request),
|
||||
'debug': settings.DEBUG,
|
||||
'settings': provider.settings,
|
||||
# If we ever have more APMs that can be disabled, we should iterate over the
|
||||
# disable_method_*/enable_method*-keys
|
||||
'disable_funding': 'sepa' if provider.settings.get('disable_method_sepa', as_type=bool) else '',
|
||||
'enable_funding': 'paylater' if provider.settings.get('enable_method_paylater', as_type=bool) else ''
|
||||
}
|
||||
|
||||
return template.render(ctx)
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@receiver(signal=process_response, dispatch_uid="payment_paypal_middleware_resp")
|
||||
def signal_process_response(sender, request: HttpRequest, response: HttpResponse, **kwargs):
|
||||
provider = PaypalMethod(sender)
|
||||
url = resolve(request.path_info)
|
||||
|
||||
if provider.settings.get('_enabled', as_type=bool) and (
|
||||
url.url_name == "event.order.pay.change" or
|
||||
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
|
||||
(url.namespace == "plugins:paypal" and url.url_name == "pay")
|
||||
):
|
||||
if 'Content-Security-Policy' in response:
|
||||
h = _parse_csp(response['Content-Security-Policy'])
|
||||
else:
|
||||
h = {}
|
||||
|
||||
csps = {
|
||||
'script-src': ['https://www.paypal.com', "'nonce-{}'".format(_nonce(request))],
|
||||
'frame-src': ['https://www.paypal.com', 'https://www.sandbox.paypal.com', "'nonce-{}'".format(_nonce(request))],
|
||||
'connect-src': ['https://www.paypal.com', 'https://www.sandbox.paypal.com'], # Or not - seems to only affect PayPal logging...
|
||||
'style-src': ["'nonce-{}'".format(_nonce(request))]
|
||||
}
|
||||
|
||||
_merge_csp(h, csps)
|
||||
|
||||
if h:
|
||||
response['Content-Security-Policy'] = _render_csp(h)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
settings_hierarkey.add_default('payment_paypal_debug_buyer_country', '', str)
|
||||
settings_hierarkey.add_default('payment_paypal_method_wallet', True, bool)
|
||||
|
||||
|
||||
def _nonce(request):
|
||||
if not hasattr(request, "_paypal_nonce"):
|
||||
request._paypal_nonce = get_random_string(32)
|
||||
return request._paypal_nonce
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
/*global $, paypal_client_id, paypal_loadingmessage, gettext */
|
||||
'use strict';
|
||||
|
||||
var pretixpaypal = {
|
||||
paypal: null,
|
||||
client_id: null,
|
||||
order_id: null,
|
||||
payer_id: null,
|
||||
merchant_id: null,
|
||||
currency: null,
|
||||
method: null,
|
||||
additional_disabled_funding: null,
|
||||
additional_enabled_funding: null,
|
||||
debug_buyer_country: null,
|
||||
continue_button: null,
|
||||
paypage: false,
|
||||
method_map: {
|
||||
wallet: {
|
||||
method: 'wallet',
|
||||
funding_source: 'paypal',
|
||||
//disable_funding: null,
|
||||
//enable_funding: 'paylater',
|
||||
early_auth: true,
|
||||
},
|
||||
apm: {
|
||||
method: 'apm',
|
||||
funding_source: null,
|
||||
//disable_funding: null,
|
||||
//enable_funding: null,
|
||||
early_auth: false,
|
||||
}
|
||||
},
|
||||
apm_map: {
|
||||
paypal: gettext('PayPal'),
|
||||
venmo: gettext('Venmo'),
|
||||
applepay: gettext('Apple Pay'),
|
||||
itau: gettext('Itaú'),
|
||||
credit: gettext('PayPal Credit'),
|
||||
card: gettext('Credit Card'),
|
||||
paylater: gettext('PayPal Pay Later'),
|
||||
ideal: gettext('iDEAL'),
|
||||
sepa: gettext('SEPA Direct Debit'),
|
||||
bancontact: gettext('Bancontact'),
|
||||
giropay: gettext('giropay'),
|
||||
sofort: gettext('SOFORT'),
|
||||
eps: gettext('eps'),
|
||||
mybank: gettext('MyBank'),
|
||||
p24: gettext('Przelewy24'),
|
||||
verkkopankki: gettext('Verkkopankki'),
|
||||
payu: gettext('PayU'),
|
||||
blik: gettext('BLIK'),
|
||||
trustly: gettext('Trustly'),
|
||||
zimpler: gettext('Zimpler'),
|
||||
maxima: gettext('Maxima'),
|
||||
oxxo: gettext('OXXO'),
|
||||
boleto: gettext('Boleto'),
|
||||
wechatpay: gettext('WeChat Pay'),
|
||||
mercadopago: gettext('Mercado Pago')
|
||||
},
|
||||
|
||||
load: function () {
|
||||
if (pretixpaypal.paypal === null) {
|
||||
pretixpaypal.client_id = $.trim($("#paypal_client_id").html());
|
||||
pretixpaypal.merchant_id = $.trim($("#paypal_merchant_id").html());
|
||||
pretixpaypal.debug_buyer_country = $.trim($("#paypal_buyer_country").html());
|
||||
pretixpaypal.continue_button = $('.checkout-button-row').closest("form").find(".checkout-button-row .btn-primary");
|
||||
pretixpaypal.continue_button.closest('div').append('<div id="paypal-button-container"></div>');
|
||||
pretixpaypal.additional_disabled_funding = $.trim($("#paypal_disable_funding").html());
|
||||
pretixpaypal.additional_enabled_funding = $.trim($("#paypal_enable_funding").html());
|
||||
pretixpaypal.paypage = Boolean($('#paypal-button-container').data('paypage'));
|
||||
pretixpaypal.order_id = $.trim($("#paypal_oid").html());
|
||||
pretixpaypal.currency = $("body").attr("data-currency");
|
||||
}
|
||||
|
||||
pretixpaypal.continue_button.prop("disabled", true);
|
||||
|
||||
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
||||
let apmtextselector = $("label[for=input_payment_paypal_apm]");
|
||||
apmtextselector.prepend('<span class="fa fa-cog fa-spin"></span> ');
|
||||
|
||||
let sdk_url = 'https://www.paypal.com/sdk/js' +
|
||||
'?client-id=' + pretixpaypal.client_id +
|
||||
'&components=buttons,funding-eligibility' +
|
||||
'¤cy=' + pretixpaypal.currency;
|
||||
|
||||
if (pretixpaypal.merchant_id) {
|
||||
sdk_url += '&merchant-id=' + pretixpaypal.merchant_id;
|
||||
}
|
||||
|
||||
if (pretixpaypal.additional_disabled_funding) {
|
||||
sdk_url += '&disable-funding=' + [pretixpaypal.additional_disabled_funding].filter(Boolean).join(',');
|
||||
}
|
||||
|
||||
if (pretixpaypal.additional_enabled_funding) {
|
||||
sdk_url += '&enable-funding=' + [pretixpaypal.additional_enabled_funding].filter(Boolean).join(',');
|
||||
}
|
||||
|
||||
if (pretixpaypal.debug_buyer_country) {
|
||||
sdk_url += '&buyer-country=' + pretixpaypal.debug_buyer_country;
|
||||
}
|
||||
|
||||
let ppscript = document.createElement('script');
|
||||
let ready = false;
|
||||
let head = document.getElementsByTagName("head")[0];
|
||||
ppscript.setAttribute('src', sdk_url);
|
||||
ppscript.setAttribute('data-csp-nonce', $.trim($("#csp_nonce").html()));
|
||||
ppscript.setAttribute('data-page-type', 'checkout');
|
||||
ppscript.setAttribute('data-partner-attribution-id', 'ramiioGmbH_Cart_PPCP');
|
||||
document.head.appendChild(ppscript);
|
||||
|
||||
ppscript.onload = ppscript.onreadystatechange = function () {
|
||||
if (!ready && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
|
||||
ready = true;
|
||||
|
||||
pretixpaypal.paypal = paypal;
|
||||
|
||||
// Handle memory leak in IE
|
||||
ppscript.onload = ppscript.onreadystatechange = null;
|
||||
if (head && ppscript.parentNode) {
|
||||
head.removeChild(ppscript);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
if ($("input[name=payment][value=paypal_apm]").length > 0) {
|
||||
pretixpaypal.renderAPMs();
|
||||
}
|
||||
|
||||
$("input[name=payment][value^='paypal']").change(function () {
|
||||
pretixpaypal.renderButton($(this).val());
|
||||
});
|
||||
|
||||
$("input[name=payment]").not("[value^='paypal']").change(function () {
|
||||
pretixpaypal.restore();
|
||||
});
|
||||
|
||||
if ($("input[name=payment][value^='paypal']").is(':checked') || $(".payment-redo-form").length) {
|
||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
||||
}
|
||||
|
||||
if ($('#paypal-button-container').data('paypage')) {
|
||||
pretixpaypal.renderButton('paypal_apm');
|
||||
}
|
||||
},
|
||||
|
||||
restore: function () {
|
||||
// if PayPal has not been initialized, there shouldn't be anything to cleanup
|
||||
if (pretixpaypal.paypal !== null) {
|
||||
$('#paypal-button-container').empty()
|
||||
pretixpaypal.continue_button.text(gettext('Continue'));
|
||||
pretixpaypal.continue_button.show();
|
||||
pretixpaypal.continue_button.prop("disabled", false);
|
||||
}
|
||||
},
|
||||
|
||||
renderButton: function (method) {
|
||||
if (method === 'paypal') {
|
||||
method = "wallet"
|
||||
} else {
|
||||
method = method.split('paypal_').at(-1)
|
||||
}
|
||||
pretixpaypal.method = pretixpaypal.method_map[method];
|
||||
|
||||
if (pretixpaypal.method.method === 'apm' && !pretixpaypal.paypage) {
|
||||
pretixpaypal.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#paypal-button-container').empty()
|
||||
$('#paypal-card-container').empty()
|
||||
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: pretixpaypal.method.funding_source,
|
||||
style: {
|
||||
layout: pretixpaypal.method.early_auth ? 'horizontal' : 'vertical',
|
||||
//color: 'white',
|
||||
shape: 'rect',
|
||||
label: 'pay',
|
||||
tagline: false
|
||||
},
|
||||
createOrder: function (data, actions) {
|
||||
if (pretixpaypal.order_id) {
|
||||
return pretixpaypal.order_id;
|
||||
}
|
||||
|
||||
// On the paypal:pay view, we already pregenerated the OID.
|
||||
// Since this view is also only used for APMs, we only need the XHR-calls for the Smart Payment Buttons.
|
||||
if (pretixpaypal.paypage) {
|
||||
return $("#payment_paypal_" + pretixpaypal.method.method + "_oid");
|
||||
} else {
|
||||
var xhrurl = $("#payment_paypal_" + pretixpaypal.method.method + "_xhr").val();
|
||||
}
|
||||
|
||||
return fetch(xhrurl, {
|
||||
method: 'POST'
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (data) {
|
||||
return data.id;
|
||||
});
|
||||
},
|
||||
onApprove: function (data, actions) {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
pretixpaypal.order_id = data.orderID;
|
||||
pretixpaypal.payer_id = data.payerID;
|
||||
|
||||
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
|
||||
let selectorstub = "#payment_paypal_" + method;
|
||||
var $form = $(selectorstub + "_oid").closest("form");
|
||||
// Insert the tokens into the form so it gets submitted to the server
|
||||
$(selectorstub + "_oid").val(pretixpaypal.order_id);
|
||||
$(selectorstub + "_payer").val(pretixpaypal.payer_id);
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
|
||||
// billingToken: null
|
||||
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
|
||||
// orderID: "7RF70259NY7589848"
|
||||
// payerID: "8M3BU92Z97VXA"
|
||||
// paymentID: null
|
||||
},
|
||||
});
|
||||
|
||||
if (button.isEligible()) {
|
||||
button.render('#paypal-button-container');
|
||||
pretixpaypal.continue_button.hide();
|
||||
} else {
|
||||
pretixpaypal.continue_button.text(gettext('Payment method unavailable'));
|
||||
pretixpaypal.continue_button.show();
|
||||
}
|
||||
},
|
||||
|
||||
renderAPMs: function () {
|
||||
pretixpaypal.restore();
|
||||
let inputselector = $("input[name=payment][value=paypal_apm]");
|
||||
// The first selector is used on the regular payment-step of the checkout flow
|
||||
// The second selector is used for the payment method change view.
|
||||
// In the long run, the layout of both pages should be adjusted to be one.
|
||||
let textselector = $("label[for=input_payment_paypal_apm]");
|
||||
let textselector2 = inputselector.next("strong");
|
||||
let eligibles = [];
|
||||
|
||||
pretixpaypal.paypal.getFundingSources().forEach(function (fundingSource) {
|
||||
// Let's always skip PayPal, since it's always a dedicated funding source
|
||||
if (fundingSource === 'paypal') {
|
||||
return;
|
||||
}
|
||||
|
||||
// This could also be paypal.Marks() - but they only expose images instead of cleartext...
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: fundingSource
|
||||
});
|
||||
|
||||
if (button.isEligible()) {
|
||||
eligibles.push(gettext(pretixpaypal.apm_map[fundingSource] || fundingSource));
|
||||
}
|
||||
});
|
||||
|
||||
inputselector.attr('title', eligibles.join(', '));
|
||||
textselector.fadeOut(300, function () {
|
||||
textselector.text(eligibles.join(', '));
|
||||
textselector.fadeIn(300);
|
||||
});
|
||||
textselector2.fadeOut(300, function () {
|
||||
textselector2[0].textContent = eligibles.join(', ');
|
||||
textselector2.fadeIn(300);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(function () {
|
||||
pretixpaypal.load();
|
||||
|
||||
(async() => {
|
||||
while(!pretixpaypal.paypal)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
pretixpaypal.ready();
|
||||
})();
|
||||
});
|
||||
@@ -1,14 +1,6 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
{% if method == "wallet" %}
|
||||
{% blocktrans trimmed %}
|
||||
The total amount listed above will be withdrawn from your PayPal account after the
|
||||
confirmation of your purchase.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
After placing your order, you will be able to select your desired payment method, including PayPal.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>{% blocktrans trimmed %}
|
||||
The total amount listed above will be withdrawn from your PayPal account after the
|
||||
confirmation of your purchase.
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
{% if method == "wallet" %}
|
||||
{% blocktrans trimmed %}
|
||||
Please click the "Pay with PayPal" button below to start your payment.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
After you clicked continue, we will redirect you to PayPal to fill in your payment
|
||||
details. You will then be redirected back here to review and confirm your order.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<input type="hidden" name="payment_paypal_{{ method }}_oid" value="" id="payment_paypal_{{ method }}_oid" />
|
||||
<input type="hidden" name="payment_paypal_{{ method }}_payer" value="" id="payment_paypal_{{ method }}_payer" />
|
||||
<input type="hidden" name="payment_paypal_{{ method }}_xhr" value="{{ xhr }}" id="payment_paypal_{{ method }}_xhr" />
|
||||
<p>{% blocktrans trimmed %}
|
||||
After you clicked continue, we will redirect you to PayPal to fill in your payment
|
||||
details. You will then be redirected back here to review and confirm your order.
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
{% if payment_info %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Order ID" %}</dt>
|
||||
<dt>{% trans "Payment ID" %}</dt>
|
||||
<dd>{{ payment_info.id }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ payment_info.status }}</dd>
|
||||
<dt>{% trans "Sale ID" %}</dt>
|
||||
<dd>{{ sale_id|default_if_none:"?" }}</dd>
|
||||
<dt>{% trans "Payer" %}</dt>
|
||||
<dd>{{ payment_info.payer.email_address }}</dd>
|
||||
<dd>{{ payment_info.payer.payer_info.email }}</dd>
|
||||
<dt>{% trans "Last update" %}</dt>
|
||||
<dd>{{ payment_info.purchase_units.0.payments.captures.0.update_time }}</dd>
|
||||
<dd>{{ payment_info.update_time }}</dd>
|
||||
<dt>{% trans "Total value" %}</dt>
|
||||
<dd>{{ payment_info.purchase_units.0.payments.captures.0.amount.value }}</dd>
|
||||
<dd>{{ payment_info.transactions.0.amount.total }}</dd>
|
||||
<dt>{% trans "Currency" %}</dt>
|
||||
<dd>{{ payment_info.purchase_units.0.payments.captures.0.amount.currency_code }}</dd>
|
||||
<dd>{{ payment_info.transactions.0.amount.currency }}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if payment_info %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Payment ID" %}</dt>
|
||||
<dd>{{ payment_info.id }}</dd>
|
||||
<dt>{% trans "Sale ID" %}</dt>
|
||||
<dd>{{ sale_id|default_if_none:"?" }}</dd>
|
||||
<dt>{% trans "Payer" %}</dt>
|
||||
<dd>{{ payment_info.payer.payer_info.email }}</dd>
|
||||
<dt>{% trans "Last update" %}</dt>
|
||||
<dd>{{ payment_info.update_time }}</dd>
|
||||
<dt>{% trans "Total value" %}</dt>
|
||||
<dd>{{ payment_info.transactions.0.amount.total }}</dd>
|
||||
<dt>{% trans "Currency" %}</dt>
|
||||
<dd>{{ payment_info.transactions.0.amount.currency }}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
@@ -1,45 +0,0 @@
|
||||
{% extends "pretixpresale/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Pay order" %}{% endblock %}
|
||||
{% block custom_header %}
|
||||
{{ block.super }}
|
||||
{% if oid %}
|
||||
<script type="text/plain" id="paypal_oid">{{ oid }}</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
Pay order: {{ code }}
|
||||
{% endblocktrans %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body" id="paymentcontainer">
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<noscript>
|
||||
<div class="alert alert-warning">
|
||||
{% trans "Please turn on JavaScript." %}
|
||||
</div>
|
||||
</noscript>
|
||||
<p>{% trans "Please use the button/form below to complete your payment." %}</p>
|
||||
<div id="paypal-button-container" data-paypage="paypal_apm" class="text-center"></div>
|
||||
<input type="hidden" name="payment_paypal_{{ method }}_oid" value="{{ oid }}" id="payment_paypal_{{ method }}_oid" />
|
||||
<input type="hidden" name="payment_paypal_{{ method }}_payer" value="" id="payment_paypal_{{ method }}_payer" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row checkout-button-row">
|
||||
<div class="col-md-4">
|
||||
<a class="btn btn-block btn-default btn-lg"
|
||||
href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,20 +0,0 @@
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixplugins/paypal/pretix-paypal.js" %}"></script>
|
||||
{% endcompress %}
|
||||
|
||||
<script type="text/plain" id="csp_nonce">{{ csp_nonce }}</script>
|
||||
<script type="text/plain" id="paypal_client_id">{{ client_id }}</script>
|
||||
<script type="text/plain" id="paypal_merchant_id">{{ merchant_id }}</script>
|
||||
{% if disable_funding %}
|
||||
<script type="text/plain" id="paypal_disable_funding">{{ disable_funding }}</script>
|
||||
{% endif %}
|
||||
{% if enable_funding %}
|
||||
<script type="text/plain" id="paypal_enable_funding">{{ enable_funding }}</script>
|
||||
{% endif %}
|
||||
{% if debug %}
|
||||
<script type="text/plain" id="paypal_buyer_country">{{ settings.debug_buyer_country }}</script>
|
||||
{% endif %}
|
||||
@@ -24,8 +24,7 @@ from django.conf.urls import include, re_path
|
||||
from pretix.multidomain import event_url
|
||||
|
||||
from .views import (
|
||||
PayView, XHRView, abort, isu_disconnect, isu_return, redirect_view,
|
||||
success, webhook,
|
||||
abort, oauth_disconnect, oauth_return, redirect_view, success, webhook,
|
||||
)
|
||||
|
||||
event_patterns = [
|
||||
@@ -33,21 +32,17 @@ event_patterns = [
|
||||
re_path(r'^abort/$', abort, name='abort'),
|
||||
re_path(r'^return/$', success, name='return'),
|
||||
re_path(r'^redirect/$', redirect_view, name='redirect'),
|
||||
re_path(r'^xhr/$', XHRView.as_view(), name='xhr'),
|
||||
re_path(r'^pay/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[^/]+)/$', PayView.as_view(), name='pay'),
|
||||
re_path(r'^(?P<order>[^/][^w]+)/(?P<secret>[A-Za-z0-9]+)/xhr/$', XHRView.as_view(), name='xhr'),
|
||||
|
||||
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/abort/', abort, name='abort'),
|
||||
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/return/', success, name='return'),
|
||||
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/xhr/', XHRView.as_view(), name='xhr'),
|
||||
|
||||
event_url(r'^webhook/$', webhook, name='webhook', require_live=False),
|
||||
])),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/paypal/disconnect/', isu_disconnect,
|
||||
name='isu.disconnect'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/paypal/return/$', isu_return, name='isu.return'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/paypal/disconnect/',
|
||||
oauth_disconnect, name='oauth.disconnect'),
|
||||
re_path(r'^_paypal/webhook/$', webhook, name='webhook'),
|
||||
re_path(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'),
|
||||
]
|
||||
|
||||
@@ -31,73 +31,36 @@
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
import hashlib
|
||||
|
||||
import json
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
import paypalrestsdk
|
||||
import paypalrestsdk.exceptions
|
||||
from django.contrib import messages
|
||||
from django.core import signing
|
||||
from django.db.models import Sum
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
|
||||
)
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.generic import TemplateView
|
||||
from django_scopes import scopes_disabled
|
||||
from paypalcheckoutsdk import orders as pp_orders, payments as pp_payments
|
||||
from paypalrestsdk.openid_connect import Tokeninfo
|
||||
|
||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.control.permissions import event_permission_required
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.plugins.paypal.client.customer.partners_merchantintegrations_get_request import (
|
||||
PartnersMerchantIntegrationsGetRequest,
|
||||
)
|
||||
from pretix.plugins.paypal.models import ReferencedPayPalObject
|
||||
from pretix.plugins.paypal.payment import PaypalMethod, PaypalMethod as Paypal
|
||||
from pretix.presale.views import get_cart, get_cart_total
|
||||
from pretix.plugins.paypal.payment import Paypal
|
||||
|
||||
logger = logging.getLogger('pretix.plugins.paypal')
|
||||
|
||||
|
||||
class PaypalOrderView:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
self.order = request.event.orders.get(code=kwargs['order'])
|
||||
if hashlib.sha1(self.order.secret.lower().encode()).hexdigest() != kwargs['hash'].lower():
|
||||
raise Http404('Unknown order')
|
||||
except Order.DoesNotExist:
|
||||
# Do a hash comparison as well to harden timing attacks
|
||||
if 'abcdefghijklmnopq'.lower() == hashlib.sha1('abcdefghijklmnopq'.encode()).hexdigest():
|
||||
raise Http404('Unknown order')
|
||||
else:
|
||||
raise Http404('Unknown order')
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def payment(self):
|
||||
return get_object_or_404(
|
||||
self.order.payments,
|
||||
pk=self.kwargs['payment'],
|
||||
provider__istartswith='paypal',
|
||||
)
|
||||
|
||||
def _redirect_to_order(self):
|
||||
return redirect(eventreverse(self.request.event, 'presale:event.order', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.order.secret
|
||||
}) + ('?paid=yes' if self.order.status == Order.STATUS_PAID else ''))
|
||||
|
||||
|
||||
@xframe_options_exempt
|
||||
def redirect_view(request, *args, **kwargs):
|
||||
signer = signing.Signer(salt='safe-redirect')
|
||||
@@ -113,136 +76,40 @@ def redirect_view(request, *args, **kwargs):
|
||||
return r
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class XHRView(TemplateView):
|
||||
template_name = ''
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'order' in self.kwargs:
|
||||
order = self.request.event.orders.filter(code=self.kwargs['order']).select_related('event').first()
|
||||
if order:
|
||||
if order.secret.lower() == self.kwargs['secret'].lower():
|
||||
pass
|
||||
else:
|
||||
order = None
|
||||
else:
|
||||
order = None
|
||||
|
||||
prov = PaypalMethod(request.event)
|
||||
|
||||
if order:
|
||||
lp = order.payments.last()
|
||||
if lp and lp.state not in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED):
|
||||
fee = lp.fee.value - prov.calculate_fee(order.pending_sum - lp.fee.value)
|
||||
else:
|
||||
fee = prov.calculate_fee(order.pending_sum)
|
||||
|
||||
cart = {
|
||||
'positions': order.positions,
|
||||
'total': order.pending_sum,
|
||||
'fee': fee,
|
||||
}
|
||||
else:
|
||||
cart = {
|
||||
'positions': get_cart(request),
|
||||
'total': get_cart_total(request),
|
||||
'fee': prov.calculate_fee(get_cart_total(request)),
|
||||
}
|
||||
|
||||
paypal_order = prov._create_paypal_order(request, None, cart)
|
||||
r = JsonResponse(paypal_order.dict())
|
||||
r._csp_ignore = True
|
||||
return r
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class PayView(PaypalOrderView, TemplateView):
|
||||
template_name = ''
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.payment.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||
return self._redirect_to_order()
|
||||
else:
|
||||
r = render(request, 'pretixplugins/paypal/pay.html', self.get_context_data())
|
||||
return r
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.payment.payment_provider.execute_payment(request, self.payment)
|
||||
return self._redirect_to_order()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['order'] = self.order
|
||||
ctx['oid'] = self.payment.info_data['id']
|
||||
ctx['method'] = self.payment.payment_provider.method
|
||||
return ctx
|
||||
|
||||
|
||||
@scopes_disabled()
|
||||
@event_permission_required('can_change_event_settings')
|
||||
def isu_return(request, *args, **kwargs):
|
||||
getparams = ['merchantId', 'merchantIdInPayPal', 'permissionsGranted', 'accountStatus', 'consentStatus', 'productIntentID', 'isEmailConfirmed']
|
||||
sessionparams = ['payment_paypal_isu_event', 'payment_paypal_isu_tracking_id']
|
||||
if not any(k in request.GET for k in getparams) or not any(k in request.session for k in sessionparams):
|
||||
def oauth_return(request, *args, **kwargs):
|
||||
if 'payment_paypal_oauth_event' not in request.session:
|
||||
messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
|
||||
return redirect(reverse('control:index'))
|
||||
|
||||
event = get_object_or_404(Event, pk=request.session['payment_paypal_isu_event'])
|
||||
event = get_object_or_404(Event, pk=request.session['payment_paypal_oauth_event'])
|
||||
|
||||
gs = GlobalSettingsObject()
|
||||
prov = Paypal(event)
|
||||
prov.init_api()
|
||||
|
||||
try:
|
||||
req = PartnersMerchantIntegrationsGetRequest(
|
||||
gs.settings.get('payment_paypal_connect_partner_merchant_id'),
|
||||
request.GET.get('merchantIdInPayPal')
|
||||
)
|
||||
response = prov.client.execute(req)
|
||||
except IOError as e:
|
||||
tokeninfo = Tokeninfo.create(request.GET.get('code'))
|
||||
userinfo = Tokeninfo.create_with_refresh_token(tokeninfo['refresh_token']).userinfo()
|
||||
except paypalrestsdk.exceptions.ConnectionError:
|
||||
logger.exception('Failed to obtain OAuth token')
|
||||
messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
|
||||
logger.exception('PayPal PartnersMerchantIntegrationsGetRequest: {}'.format(str(e)))
|
||||
else:
|
||||
params = ['merchant_id', 'tracking_id', 'payments_receivable', 'primary_email_confirmed']
|
||||
if not any(k in response.result for k in params):
|
||||
if 'message' in response.result:
|
||||
messages.error(request, response.result.message)
|
||||
else:
|
||||
messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
|
||||
else:
|
||||
if response.result.tracking_id != request.session['payment_paypal_isu_tracking_id']:
|
||||
messages.error(request, _('An error occurred during connecting with PayPal, please try again.'))
|
||||
else:
|
||||
if request.GET.get("isEmailConfirmed") == "false": # Yes - literal!
|
||||
messages.warning(
|
||||
request,
|
||||
_('The e-mail address on your PayPal account has not yet been confirmed. You will need to do '
|
||||
'this before you can start accepting payments.')
|
||||
)
|
||||
messages.success(
|
||||
request,
|
||||
_('Your PayPal account is now connected to pretix. You can change the settings in detail below.')
|
||||
)
|
||||
messages.success(request,
|
||||
_('Your PayPal account is now connected to pretix. You can change the settings in '
|
||||
'detail below.'))
|
||||
|
||||
event.settings.payment_paypal_isu_merchant_id = response.result.merchant_id
|
||||
|
||||
# Just for good measure: Let's keep a copy of the granted scopes
|
||||
for integration in response.result.oauth_integrations:
|
||||
if integration.integration_type == 'OAUTH_THIRD_PARTY':
|
||||
for third_party in integration.oauth_third_party:
|
||||
if third_party.partner_client_id == prov.client.environment.client_id:
|
||||
event.settings.payment_paypal_isu_scopes = third_party.scopes
|
||||
event.settings.payment_paypal_connect_refresh_token = tokeninfo['refresh_token']
|
||||
event.settings.payment_paypal_connect_user_id = userinfo.email
|
||||
|
||||
return redirect(reverse('control:event.settings.payment.provider', kwargs={
|
||||
'organizer': event.organizer.slug,
|
||||
'event': event.slug,
|
||||
'provider': 'paypal_settings'
|
||||
'provider': 'paypal'
|
||||
}))
|
||||
|
||||
|
||||
def success(request, *args, **kwargs):
|
||||
pid = request.GET.get('paymentId')
|
||||
token = request.GET.get('token')
|
||||
payer = request.GET.get('PayerID')
|
||||
request.session['payment_paypal_token'] = token
|
||||
@@ -257,7 +124,7 @@ def success(request, *args, **kwargs):
|
||||
else:
|
||||
payment = None
|
||||
|
||||
if request.session.get('payment_paypal_id', None):
|
||||
if pid == request.session.get('payment_paypal_id', None):
|
||||
if payment:
|
||||
prov = Paypal(request.event)
|
||||
try:
|
||||
@@ -311,20 +178,18 @@ def webhook(request, *args, **kwargs):
|
||||
# We do not check the signature, we just use it as a trigger to look the charge up.
|
||||
if 'resource_type' not in event_json:
|
||||
return HttpResponse("Invalid body, no resource_type given", status=400)
|
||||
|
||||
if event_json['resource_type'] not in ["checkout-order", "refund", "capture"]:
|
||||
if event_json['resource_type'] not in ('sale', 'refund'):
|
||||
return HttpResponse("Not interested in this resource type", status=200)
|
||||
|
||||
# Retrieve the Charge ID of the refunded payment
|
||||
if event_json['resource_type'] == 'refund':
|
||||
payloadid = get_link(event_json['resource']['links'], 'up')['href'].split('/')[-1]
|
||||
if event_json['resource_type'] == 'sale':
|
||||
saleid = event_json['resource']['id']
|
||||
else:
|
||||
payloadid = event_json['resource']['id']
|
||||
saleid = event_json['resource']['sale_id']
|
||||
|
||||
try:
|
||||
refs = [payloadid]
|
||||
if event_json['resource'].get('supplementary_data', {}).get('related_ids', {}).get('order_id'):
|
||||
refs.append(event_json['resource'].get('supplementary_data').get('related_ids').get('order_id'))
|
||||
refs = [saleid]
|
||||
if event_json['resource'].get('parent_payment'):
|
||||
refs.append(event_json['resource'].get('parent_payment'))
|
||||
|
||||
rso = ReferencedPayPalObject.objects.select_related('order', 'order__event').get(
|
||||
reference__in=refs
|
||||
@@ -341,10 +206,8 @@ def webhook(request, *args, **kwargs):
|
||||
prov.init_api()
|
||||
|
||||
try:
|
||||
if rso:
|
||||
payloadid = rso.payment.info_data['id']
|
||||
sale = prov.client.execute(pp_orders.OrdersGetRequest(payloadid)).result
|
||||
except IOError:
|
||||
sale = paypalrestsdk.Sale.find(saleid)
|
||||
except paypalrestsdk.exceptions.ConnectionError:
|
||||
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
|
||||
return HttpResponse('Sale not found', status=500)
|
||||
|
||||
@@ -355,58 +218,47 @@ def webhook(request, *args, **kwargs):
|
||||
info__icontains=sale['id'])
|
||||
payment = None
|
||||
for p in payments:
|
||||
# Legacy PayPal info-data
|
||||
if "purchase_units" not in p.info_data:
|
||||
try:
|
||||
req = pp_orders.OrdersGetRequest(p.info_data['cart'])
|
||||
response = prov.client.execute(req)
|
||||
p.info = json.dumps(response.result.dict())
|
||||
p.save(update_fields=['info'])
|
||||
p.refresh_from_db()
|
||||
except IOError:
|
||||
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
|
||||
return HttpResponse('Could not retrieve Order Data', status=500)
|
||||
|
||||
for res in p.info_data['purchase_units'][0]['payments']['captures']:
|
||||
if res['status'] in ['COMPLETED', 'PARTIALLY_REFUNDED'] and res['id'] == sale['id']:
|
||||
payment = p
|
||||
break
|
||||
payment_info = p.info_data
|
||||
for res in payment_info['transactions'][0]['related_resources']:
|
||||
for k, v in res.items():
|
||||
if k == 'sale' and v['id'] == sale['id']:
|
||||
payment = p
|
||||
break
|
||||
|
||||
if not payment:
|
||||
return HttpResponse('Payment not found', status=200)
|
||||
|
||||
payment.order.log_action('pretix.plugins.paypal.event', data=event_json)
|
||||
|
||||
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED and sale['status'] in ('PARTIALLY_REFUNDED', 'REFUNDED', 'COMPLETED'):
|
||||
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED and sale['state'] in ('partially_refunded', 'refunded'):
|
||||
if event_json['resource_type'] == 'refund':
|
||||
try:
|
||||
req = pp_payments.RefundsGetRequest(event_json['resource']['id'])
|
||||
refund = prov.client.execute(req).result
|
||||
except IOError:
|
||||
refund = paypalrestsdk.Refund.find(event_json['resource']['id'])
|
||||
except paypalrestsdk.exceptions.ConnectionError:
|
||||
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
|
||||
return HttpResponse('Refund not found', status=500)
|
||||
|
||||
known_refunds = {r.info_data.get('id'): r for r in payment.refunds.all()}
|
||||
if refund['id'] not in known_refunds:
|
||||
payment.create_external_refund(
|
||||
amount=abs(Decimal(refund['amount']['value'])),
|
||||
info=json.dumps(refund.dict() if not isinstance(refund, dict) else refund)
|
||||
amount=abs(Decimal(refund['amount']['total'])),
|
||||
info=json.dumps(refund.to_dict() if not isinstance(refund, dict) else refund)
|
||||
)
|
||||
elif known_refunds.get(refund['id']).state in (
|
||||
OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT) and refund['status'] == 'COMPLETED':
|
||||
OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT) and refund['state'] == 'completed':
|
||||
known_refunds.get(refund['id']).done()
|
||||
|
||||
if 'seller_payable_breakdown' in refund and 'total_refunded_amount' in refund['seller_payable_breakdown']:
|
||||
if 'total_refunded_amount' in refund:
|
||||
known_sum = payment.refunds.filter(
|
||||
state__in=(OrderRefund.REFUND_STATE_DONE, OrderRefund.REFUND_STATE_TRANSIT,
|
||||
OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_SOURCE_EXTERNAL)
|
||||
).aggregate(s=Sum('amount'))['s'] or Decimal('0.00')
|
||||
total_refunded_amount = Decimal(refund['seller_payable_breakdown']['total_refunded_amount']['value'])
|
||||
total_refunded_amount = Decimal(refund['total_refunded_amount']['value'])
|
||||
if known_sum < total_refunded_amount:
|
||||
payment.create_external_refund(
|
||||
amount=total_refunded_amount - known_sum
|
||||
)
|
||||
elif sale['status'] == 'REFUNDED':
|
||||
elif sale['state'] == 'refunded':
|
||||
known_sum = payment.refunds.filter(
|
||||
state__in=(OrderRefund.REFUND_STATE_DONE, OrderRefund.REFUND_STATE_TRANSIT,
|
||||
OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_SOURCE_EXTERNAL)
|
||||
@@ -417,8 +269,7 @@ def webhook(request, *args, **kwargs):
|
||||
amount=payment.amount - known_sum
|
||||
)
|
||||
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) \
|
||||
and sale['status'] == 'COMPLETED':
|
||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) and sale['state'] == 'completed':
|
||||
try:
|
||||
payment.confirm()
|
||||
except Quota.QuotaExceededException:
|
||||
@@ -429,24 +280,14 @@ def webhook(request, *args, **kwargs):
|
||||
|
||||
@event_permission_required('can_change_event_settings')
|
||||
@require_POST
|
||||
def isu_disconnect(request, **kwargs):
|
||||
def oauth_disconnect(request, **kwargs):
|
||||
del request.event.settings.payment_paypal_connect_refresh_token
|
||||
del request.event.settings.payment_paypal_connect_user_id
|
||||
del request.event.settings.payment_paypal_isu_merchant_id
|
||||
del request.event.settings.payment_paypal_isu_scopes
|
||||
request.event.settings.payment_paypal__enabled = False
|
||||
messages.success(request, _('Your PayPal account has been disconnected.'))
|
||||
|
||||
return redirect(reverse('control:event.settings.payment.provider', kwargs={
|
||||
'organizer': request.event.organizer.slug,
|
||||
'event': request.event.slug,
|
||||
'provider': 'paypal_settings'
|
||||
'provider': 'paypal'
|
||||
}))
|
||||
|
||||
|
||||
def get_link(links, rel):
|
||||
for link in links:
|
||||
if link['rel'] == rel:
|
||||
return link
|
||||
|
||||
return None
|
||||
|
||||
@@ -203,8 +203,7 @@ setup(
|
||||
'oauthlib==3.1.*',
|
||||
'openpyxl==3.0.*',
|
||||
'packaging',
|
||||
'paypal-checkout-serversdk==1.0.*',
|
||||
'PyJWT==2.0.*',
|
||||
'paypalrestsdk==1.13.*',
|
||||
'phonenumberslite==8.12.*',
|
||||
'Pillow==9.1.*',
|
||||
'protobuf==3.19.*',
|
||||
|
||||
@@ -67,55 +67,15 @@ def env(client):
|
||||
return client, ticket
|
||||
|
||||
|
||||
class Object():
|
||||
pass
|
||||
|
||||
|
||||
def get_test_order():
|
||||
return {'id': '04F89033701558004',
|
||||
'intent': 'CAPTURE',
|
||||
'status': 'APPROVED',
|
||||
'purchase_units': [{'reference_id': 'default',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'payee': {'merchant_id': 'G6R2B9YXADKWW'},
|
||||
'description': 'Event tickets for PayPal v2',
|
||||
'custom_id': 'PAYPALV2',
|
||||
'soft_descriptor': 'MARTINFACIL'}],
|
||||
'payer': {'name': {'given_name': 'test', 'surname': 'buyer'},
|
||||
'email_address': 'dummy@dummy.dummy',
|
||||
'payer_id': 'Q739JNKWH67HE',
|
||||
'address': {'country_code': 'DE'}},
|
||||
'create_time': '2022-04-28T13:10:58Z',
|
||||
'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/04F89033701558004',
|
||||
'rel': 'self',
|
||||
'method': 'GET'},
|
||||
{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/04F89033701558004',
|
||||
'rel': 'update',
|
||||
'method': 'PATCH'},
|
||||
{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/04F89033701558004/capture',
|
||||
'rel': 'capture',
|
||||
'method': 'POST'}]}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment(env, monkeypatch):
|
||||
def init_api(self):
|
||||
class Client():
|
||||
environment = Object()
|
||||
environment.client_id = '12345'
|
||||
environment.merchant_id = 'G6R2B9YXADKWW'
|
||||
|
||||
def execute(self, request):
|
||||
response = Object()
|
||||
response.result = Object()
|
||||
response.result.status = 'APPROVED'
|
||||
return response
|
||||
|
||||
self.client = Client()
|
||||
|
||||
order = get_test_order()
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
def create_payment(self, request, payment):
|
||||
assert payment['intent'] == 'sale'
|
||||
assert payment['transactions'][0]['amount']['currency'] == 'EUR'
|
||||
assert payment['transactions'][0]['amount']['total'] == '26.00'
|
||||
create_payment.called = True
|
||||
return 'https://approve.url'
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal._create_payment", create_payment)
|
||||
|
||||
client, ticket = env
|
||||
session_key = get_cart_session_key(client, ticket.event)
|
||||
@@ -127,15 +87,7 @@ def test_payment(env, monkeypatch):
|
||||
client.post('/%s/%s/checkout/questions/' % (ticket.event.organizer.slug, ticket.event.slug), {
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
|
||||
session = client.session
|
||||
session['payment_paypal_oid'] = '04F89033701558004'
|
||||
session.save()
|
||||
|
||||
response = client.post('/%s/%s/checkout/payment/' % (ticket.event.organizer.slug, ticket.event.slug), {
|
||||
'payment': 'paypal',
|
||||
'payment_paypal_wallet_oid': '04F89033701558004',
|
||||
'payment_paypal_wallet_payer': 'Q739JNKWH67HE',
|
||||
'payment': 'paypal'
|
||||
})
|
||||
print(response.content.decode())
|
||||
assert response['Location'] == '/ccc/30c3/checkout/confirm/'
|
||||
assert response['Location'] == 'https://approve.url'
|
||||
|
||||
@@ -61,7 +61,7 @@ def env(client):
|
||||
@pytest.mark.django_db
|
||||
def test_settings(env):
|
||||
client, event = env
|
||||
response = client.get('/control/event/%s/%s/settings/payment/paypal_settings' % (event.organizer.slug, event.slug),
|
||||
response = client.get('/control/event/%s/%s/settings/payment/paypal' % (event.organizer.slug, event.slug),
|
||||
follow=True)
|
||||
assert response.status_code == 200
|
||||
assert 'paypal__enabled' in response.rendered_content
|
||||
|
||||
@@ -48,348 +48,190 @@ def env():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('43.59'),
|
||||
total=Decimal('13.37'),
|
||||
)
|
||||
o1.payments.create(
|
||||
amount=o1.total,
|
||||
provider='paypal',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
info=json.dumps({
|
||||
"id": "806440346Y391300T",
|
||||
"status": "COMPLETED",
|
||||
"purchase_units": [
|
||||
"id": "PAY-5YK922393D847794YKER7MUI",
|
||||
"create_time": "2013-02-19T22:01:53Z",
|
||||
"update_time": "2013-02-19T22:01:55Z",
|
||||
"state": "approved",
|
||||
"intent": "sale",
|
||||
"payer": {
|
||||
"payment_method": "credit_card",
|
||||
"funding_instruments": [
|
||||
{
|
||||
"credit_card": {
|
||||
"type": "mastercard",
|
||||
"number": "xxxxxxxxxxxx5559",
|
||||
"expire_month": 2,
|
||||
"expire_year": 2018,
|
||||
"first_name": "Betsy",
|
||||
"last_name": "Buyer"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"shipping": {
|
||||
"name": {
|
||||
"full_name": "test buyer"
|
||||
"amount": {
|
||||
"total": "7.47",
|
||||
"currency": "USD",
|
||||
"details": {
|
||||
"subtotal": "7.47"
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"captures": [
|
||||
{
|
||||
"id": "22A4162004478570J",
|
||||
"status": "COMPLETED",
|
||||
"description": "This is the payment transaction description.",
|
||||
"note_to_payer": "Contact us for any questions on your order.",
|
||||
"related_resources": [
|
||||
{
|
||||
"sale": {
|
||||
"id": "36C38912MN9658832",
|
||||
"create_time": "2013-02-19T22:01:53Z",
|
||||
"update_time": "2013-02-19T22:01:55Z",
|
||||
"state": "completed",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
"total": "7.47",
|
||||
"currency": "USD"
|
||||
},
|
||||
"final_capture": True,
|
||||
"disbursement_mode": "INSTANT",
|
||||
"seller_protection": {
|
||||
"status": "ELIGIBLE",
|
||||
"dispute_categories": [
|
||||
"ITEM_NOT_RECEIVED",
|
||||
"UNAUTHORIZED_TRANSACTION"
|
||||
]
|
||||
"protection_eligibility": "ELIGIBLE",
|
||||
"protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE",
|
||||
"transaction_fee": {
|
||||
"value": "1.75",
|
||||
"currency": "USD"
|
||||
},
|
||||
"seller_receivable_breakdown": {
|
||||
"gross_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"currency_code": "EUR",
|
||||
"value": "1.18"
|
||||
},
|
||||
"net_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "42.41"
|
||||
}
|
||||
},
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J",
|
||||
"href": "https://api.paypal.com/v1/payments/sale/36C38912MN9658832",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund",
|
||||
"href": "https://api.paypal.com/v1/payments/sale/36C38912MN9658832/refund",
|
||||
"rel": "refund",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "up",
|
||||
"href":
|
||||
"https://api.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
|
||||
"rel": "parent_payment",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"create_time": "2022-04-28T12:00:22Z",
|
||||
"update_time": "2022-04-28T12:00:22Z"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"href": "https://api.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
|
||||
})
|
||||
)
|
||||
return event, o1
|
||||
|
||||
|
||||
def get_test_order():
|
||||
return {'id': '806440346Y391300T',
|
||||
'intent': 'CAPTURE',
|
||||
'status': 'COMPLETED',
|
||||
'purchase_units': [{'reference_id': 'default',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'payee': {'email_address': 'dummy-facilitator@dummy.dummy',
|
||||
'merchant_id': 'G6R2B9YXADKWW'},
|
||||
'description': 'Order JWJGC for PayPal v2',
|
||||
'custom_id': 'Order PAYPALV2-JWJGC',
|
||||
'soft_descriptor': 'MARTINFACIL',
|
||||
'payments': {'captures': [{'id': '22A4162004478570J',
|
||||
'status': 'COMPLETED',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'final_capture': True,
|
||||
'disbursement_mode': 'INSTANT',
|
||||
'seller_protection': {'status': 'ELIGIBLE',
|
||||
'dispute_categories': [
|
||||
'ITEM_NOT_RECEIVED',
|
||||
'UNAUTHORIZED_TRANSACTION']},
|
||||
'seller_receivable_breakdown': {
|
||||
'gross_amount': {'currency_code': 'EUR',
|
||||
'value': '43.59'},
|
||||
'paypal_fee': {'currency_code': 'EUR', 'value': '1.18'},
|
||||
'net_amount': {'currency_code': 'EUR',
|
||||
'value': '42.41'}},
|
||||
'custom_id': 'Order PAYPALV2-JWJGC',
|
||||
'links': [{
|
||||
'href': 'https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J',
|
||||
'rel': 'self',
|
||||
'method': 'GET'},
|
||||
{
|
||||
'href': 'https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund',
|
||||
'rel': 'refund',
|
||||
'method': 'POST'},
|
||||
{
|
||||
'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T',
|
||||
'rel': 'up',
|
||||
'method': 'GET'}],
|
||||
'create_time': '2022-04-28T12:00:22Z',
|
||||
'update_time': '2022-04-28T12:00:22Z'}]}}],
|
||||
'payer': {'name': {'given_name': 'test', 'surname': 'buyer'},
|
||||
'email_address': 'dummy@dummy.dummy',
|
||||
'payer_id': 'Q739JNKWH67HE',
|
||||
'address': {'country_code': 'DE'}},
|
||||
'create_time': '2022-04-28T11:59:59Z',
|
||||
'update_time': '2022-04-28T12:00:22Z',
|
||||
'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T',
|
||||
'rel': 'self',
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
def get_test_refund():
|
||||
def get_test_charge(order: Order):
|
||||
return {
|
||||
"id": "1YK122615V244890X",
|
||||
"id": "36C38912MN9658832",
|
||||
"create_time": "2013-02-19T22:01:53Z",
|
||||
"update_time": "2013-02-19T22:01:55Z",
|
||||
"state": "completed",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
"total": "7.47",
|
||||
"currency": "USD"
|
||||
},
|
||||
"seller_payable_breakdown": {
|
||||
"gross_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"currency_code": "EUR",
|
||||
"value": "1.18"
|
||||
},
|
||||
"net_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "42.41"
|
||||
},
|
||||
"total_refunded_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
}
|
||||
"protection_eligibility": "ELIGIBLE",
|
||||
"protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
|
||||
"transaction_fee": {
|
||||
"value": "1.75",
|
||||
"currency": "USD"
|
||||
},
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"status": "COMPLETED",
|
||||
"create_time": "2022-04-28T07:50:56-07:00",
|
||||
"update_time": "2022-04-28T07:50:56-07:00",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/refunds/1YK122615V244890X",
|
||||
"href": "https://api.paypal.com/v1/payments/sale/36C38912MN9658832",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J",
|
||||
"rel": "up",
|
||||
"href": "https://api.paypal.com/v1/payments/sale/36C38912MN9658832/refund",
|
||||
"rel": "refund",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"href": "https://api.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
|
||||
"rel": "parent_payment",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class Object():
|
||||
pass
|
||||
|
||||
|
||||
def init_api(self):
|
||||
class Client():
|
||||
environment = Object()
|
||||
environment.client_id = '12345'
|
||||
environment.merchant_id = 'G6R2B9YXADKWW'
|
||||
|
||||
def execute(self, request):
|
||||
response = Object()
|
||||
response.result = request
|
||||
return response
|
||||
|
||||
self.client = Client()
|
||||
def get_test_refund(order: Order):
|
||||
return {
|
||||
'refund_from_received_amount': {'value': '13.30', 'currency': 'EUR'},
|
||||
'amount': {'total': '13.37', 'currency': 'EUR'},
|
||||
'sale_id': '1G495778AR8401726',
|
||||
'update_time': '2018-07-24T07:50:07Z',
|
||||
'total_refunded_amount': {'value': '13.37', 'currency': 'EUR'},
|
||||
'refund_reason_code': 'REFUND',
|
||||
'invoice_number': 'Test',
|
||||
'parent_payment': 'PAY-0UB50445HE155450FLNLNMUY',
|
||||
'state': 'completed',
|
||||
'create_time': '2018-07-24T07:50:07Z',
|
||||
'refund_from_transaction_fee': {'value': '0.07', 'currency': 'EUR'},
|
||||
'id': '93M41501U3542574L',
|
||||
'refund_to_payer': {'value': '13.37', 'currency': 'EUR'},
|
||||
'links': [
|
||||
{'method': 'GET', 'rel': 'self',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/refund/93M41501U3542574L'},
|
||||
{'method': 'GET',
|
||||
'rel': 'parent_payment',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/payment/PAY-0UB50445HE155450FLNLNMUY'},
|
||||
{'method': 'GET', 'rel': 'sale',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/sale/1G495778AR8401726'}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_all_good(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
pp_order = get_test_order()
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"create_time": "2022-04-28T12:00:37.077Z",
|
||||
"resource_type": "checkout-order",
|
||||
"event_type": "CHECKOUT.ORDER.COMPLETED",
|
||||
"summary": "Checkout Order Completed",
|
||||
"id": "WH-2WR32451HC0233532-67976317FL4543714",
|
||||
"create_time": "2014-10-23T17:23:52Z",
|
||||
"resource_type": "sale",
|
||||
"event_type": "PAYMENT.SALE.COMPLETED",
|
||||
"summary": "A successful sale payment was made for $ 0.48 USD",
|
||||
"resource": {
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T11:59:59Z",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "dummy-facilitator@dummy.dummy",
|
||||
"merchant_id": "G6R2B9YXADKWW"
|
||||
},
|
||||
"description": "Order JWJGC for PayPal v2",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"soft_descriptor": "MARTINFACIL",
|
||||
"payments": {
|
||||
"captures": [
|
||||
{
|
||||
"id": "22A4162004478570J",
|
||||
"status": "COMPLETED",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"final_capture": True,
|
||||
"disbursement_mode": "INSTANT",
|
||||
"seller_protection": {
|
||||
"status": "ELIGIBLE",
|
||||
"dispute_categories": [
|
||||
"ITEM_NOT_RECEIVED",
|
||||
"UNAUTHORIZED_TRANSACTION"
|
||||
]
|
||||
},
|
||||
"seller_receivable_breakdown": {
|
||||
"gross_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"currency_code": "EUR",
|
||||
"value": "1.18"
|
||||
},
|
||||
"net_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "42.41"
|
||||
}
|
||||
},
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund",
|
||||
"rel": "refund",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "up",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"create_time": "2022-04-28T12:00:22Z",
|
||||
"update_time": "2022-04-28T12:00:22Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"id": "806440346Y391300T",
|
||||
"intent": "CAPTURE",
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
"amount": {
|
||||
"total": "-0.01",
|
||||
"currency": "USD"
|
||||
},
|
||||
"status": "COMPLETED"
|
||||
"id": "36C38912MN9658832",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"update_time": "2014-10-31T15:41:51Z",
|
||||
"state": "completed",
|
||||
"create_time": "2014-10-31T15:41:51Z",
|
||||
"links": [],
|
||||
"sale_id": "9T0916710M1105906"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
"links": [],
|
||||
"event_version": "1.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
@@ -406,133 +248,33 @@ def test_webhook_global(env, client, monkeypatch):
|
||||
with scopes_disabled():
|
||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
|
||||
pp_order = get_test_order()
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
ReferencedPayPalObject.objects.create(order=order, reference="PAY-5YK922393D847794YKER7MUI")
|
||||
|
||||
client.post('/_paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"create_time": "2022-04-28T12:00:37.077Z",
|
||||
"resource_type": "checkout-order",
|
||||
"event_type": "CHECKOUT.ORDER.COMPLETED",
|
||||
"summary": "Checkout Order Completed",
|
||||
"id": "WH-2WR32451HC0233532-67976317FL4543714",
|
||||
"create_time": "2014-10-23T17:23:52Z",
|
||||
"resource_type": "sale",
|
||||
"event_type": "PAYMENT.SALE.COMPLETED",
|
||||
"summary": "A successful sale payment was made for $ 0.48 USD",
|
||||
"resource": {
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T11:59:59Z",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "dummy-facilitator@dummy.dummy",
|
||||
"merchant_id": "G6R2B9YXADKWW"
|
||||
},
|
||||
"description": "Order JWJGC for PayPal v2",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"soft_descriptor": "MARTINFACIL",
|
||||
"payments": {
|
||||
"captures": [
|
||||
{
|
||||
"id": "22A4162004478570J",
|
||||
"status": "COMPLETED",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"final_capture": True,
|
||||
"disbursement_mode": "INSTANT",
|
||||
"seller_protection": {
|
||||
"status": "ELIGIBLE",
|
||||
"dispute_categories": [
|
||||
"ITEM_NOT_RECEIVED",
|
||||
"UNAUTHORIZED_TRANSACTION"
|
||||
]
|
||||
},
|
||||
"seller_receivable_breakdown": {
|
||||
"gross_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"currency_code": "EUR",
|
||||
"value": "1.18"
|
||||
},
|
||||
"net_amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "42.41"
|
||||
}
|
||||
},
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund",
|
||||
"rel": "refund",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "up",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"create_time": "2022-04-28T12:00:22Z",
|
||||
"update_time": "2022-04-28T12:00:22Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"id": "806440346Y391300T",
|
||||
"intent": "CAPTURE",
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
"amount": {
|
||||
"total": "-0.01",
|
||||
"currency": "USD"
|
||||
},
|
||||
"status": "COMPLETED"
|
||||
"id": "36C38912MN9658832",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"update_time": "2014-10-31T15:41:51Z",
|
||||
"state": "completed",
|
||||
"create_time": "2014-10-31T15:41:51Z",
|
||||
"links": [],
|
||||
"sale_id": "9T0916710M1105906"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
"links": [],
|
||||
"event_version": "1.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
@@ -548,93 +290,32 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
with scopes_disabled():
|
||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
|
||||
pp_order = get_test_order()
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-88L014580L300952M-4BX97184625330932",
|
||||
"create_time": "2022-04-28T12:00:26.840Z",
|
||||
"resource_type": "capture",
|
||||
"event_type": "PAYMENT.CAPTURE.COMPLETED",
|
||||
"summary": "Payment completed for EUR 43.59 EUR",
|
||||
"id": "WH-2WR32451HC0233532-67976317FL4543714",
|
||||
"create_time": "2014-10-23T17:23:52Z",
|
||||
"resource_type": "sale",
|
||||
"event_type": "PAYMENT.SALE.COMPLETED",
|
||||
"summary": "A successful sale payment was made for $ 0.48 USD",
|
||||
"resource": {
|
||||
"disbursement_mode": "INSTANT",
|
||||
"amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
"total": "-0.01",
|
||||
"currency": "USD"
|
||||
},
|
||||
"seller_protection": {
|
||||
"dispute_categories": [
|
||||
"ITEM_NOT_RECEIVED",
|
||||
"UNAUTHORIZED_TRANSACTION"
|
||||
],
|
||||
"status": "ELIGIBLE"
|
||||
},
|
||||
"supplementary_data": {
|
||||
"related_ids": {
|
||||
"order_id": "806440346Y391300T"
|
||||
}
|
||||
},
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T12:00:22Z",
|
||||
"final_capture": True,
|
||||
"seller_receivable_breakdown": {
|
||||
"paypal_fee": {
|
||||
"value": "1.18",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"gross_amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"net_amount": {
|
||||
"value": "42.41",
|
||||
"currency_code": "EUR"
|
||||
}
|
||||
},
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"links": [
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"rel": "refund",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "up",
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T"
|
||||
}
|
||||
],
|
||||
"id": "22A4162004478570J",
|
||||
"status": "COMPLETED"
|
||||
"id": "36C38912MN9658832",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"update_time": "2014-10-31T15:41:51Z",
|
||||
"state": "completed",
|
||||
"create_time": "2014-10-31T15:41:51Z",
|
||||
"links": [],
|
||||
"sale_id": "9T0916710M1105906"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-88L014580L300952M-4BX97184625330932",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-88L014580L300952M-4BX97184625330932/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
"links": [],
|
||||
"event_version": "1.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
@@ -644,82 +325,43 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_refund1(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
pp_order = get_test_order()
|
||||
pp_refund = get_test_refund()
|
||||
charge = get_test_charge(env[1])
|
||||
charge['state'] = 'refunded'
|
||||
refund = get_test_refund(env[1])
|
||||
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="22A4162004478570J")
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("paypalrestsdk.Refund.find", lambda *args: refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-5LJ60612747357339-66248625WA926672S",
|
||||
"create_time": "2022-04-28T14:51:00.318Z",
|
||||
# Sample obtained in a sandbox webhook
|
||||
"id": "WH-9K829080KA1622327-31011919VC6498738",
|
||||
"create_time": "2017-01-15T20:15:36Z",
|
||||
"resource_type": "refund",
|
||||
"event_type": "PAYMENT.CAPTURE.REFUNDED",
|
||||
"summary": "A EUR 43.59 EUR capture payment was refunded",
|
||||
"event_type": "PAYMENT.SALE.REFUNDED",
|
||||
"summary": "A EUR 255.41 EUR sale payment was refunded",
|
||||
"resource": {
|
||||
"seller_payable_breakdown": {
|
||||
"total_refunded_amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"value": "1.18",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"gross_amount": {
|
||||
"value": "42.41",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"net_amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
}
|
||||
},
|
||||
"amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
"total": "255.41",
|
||||
"currency": "EUR"
|
||||
},
|
||||
"update_time": "2022-04-28T07:50:56-07:00",
|
||||
"create_time": "2022-04-28T07:50:56-07:00",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"links": [
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/refunds/1YK122615V244890X"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "up",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J"
|
||||
}
|
||||
],
|
||||
"id": "1YK122615V244890X",
|
||||
"status": "COMPLETED"
|
||||
"id": "75S46770PP192124D",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"update_time": "2017-01-15T20:15:06Z",
|
||||
"create_time": "2017-01-15T20:14:29Z",
|
||||
"state": "completed",
|
||||
"links": [],
|
||||
"refund_to_payer": {
|
||||
"value": "255.41",
|
||||
"currency": "EUR"
|
||||
},
|
||||
"invoice_number": "",
|
||||
"refund_reason_code": "REFUND",
|
||||
"sale_id": "9T0916710M1105906"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-5LJ60612747357339-66248625WA926672S",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-5LJ60612747357339-66248625WA926672S/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
"links": [],
|
||||
"event_version": "1.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
@@ -738,82 +380,37 @@ def test_webhook_refund1(env, client, monkeypatch):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_refund2(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
pp_order = get_test_order()
|
||||
pp_refund = get_test_refund()
|
||||
charge = get_test_charge(env[1])
|
||||
charge['state'] = 'refunded'
|
||||
refund = get_test_refund(env[1])
|
||||
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("paypalcheckoutsdk.payments.RefundsGetRequest", lambda *args: pp_refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.PaypalMethod.init_api", init_api)
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="22A4162004478570J")
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("paypalrestsdk.Refund.find", lambda *args: refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-7FL378472F5218625-6WC87835CR8751809",
|
||||
"create_time": "2022-04-28T14:56:08.160Z",
|
||||
# Sample obtained in the webhook simulator
|
||||
"id": "WH-2N242548W9943490U-1JU23391CS4765624",
|
||||
"create_time": "2014-10-31T15:42:24Z",
|
||||
"resource_type": "refund",
|
||||
"event_type": "PAYMENT.CAPTURE.REFUNDED",
|
||||
"summary": "A EUR 43.59 EUR capture payment was refunded",
|
||||
"event_type": "PAYMENT.SALE.REFUNDED",
|
||||
"summary": "A 0.01 USD sale payment was refunded",
|
||||
"resource": {
|
||||
"seller_payable_breakdown": {
|
||||
"total_refunded_amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"paypal_fee": {
|
||||
"value": "01.18",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"gross_amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
"net_amount": {
|
||||
"value": "42.41",
|
||||
"currency_code": "EUR"
|
||||
}
|
||||
},
|
||||
"amount": {
|
||||
"value": "43.59",
|
||||
"currency_code": "EUR"
|
||||
"total": "-0.01",
|
||||
"currency": "USD"
|
||||
},
|
||||
"update_time": "2022-04-28T07:56:04-07:00",
|
||||
"create_time": "2022-04-28T07:56:04-07:00",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"links": [
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/refunds/3K87087190824201K"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"rel": "up",
|
||||
"href": "https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J"
|
||||
}
|
||||
],
|
||||
"id": "3K87087190824201K",
|
||||
"status": "COMPLETED"
|
||||
"id": "36C38912MN9658832",
|
||||
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
|
||||
"update_time": "2014-10-31T15:41:51Z",
|
||||
"state": "completed",
|
||||
"create_time": "2014-10-31T15:41:51Z",
|
||||
"links": [],
|
||||
"sale_id": "9T0916710M1105906"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-7FL378472F5218625-6WC87835CR8751809",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-7FL378472F5218625-6WC87835CR8751809/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
"links": [],
|
||||
"event_version": "1.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user