You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
7.5 KiB
Python
211 lines
7.5 KiB
Python
from django.shortcuts import redirect
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from urllib.parse import urlencode
|
|
from urllib.request import urlopen
|
|
from django.urls import reverse
|
|
from django.conf import settings as project_settings
|
|
|
|
from .base import BackendBase
|
|
|
|
|
|
class PaypalBackend(BackendBase):
|
|
backend_id = 'paypal'
|
|
backend_verbose_name = _("PayPal")
|
|
backend_display_name = _("PayPal")
|
|
backend_has_recurring = True
|
|
|
|
def __init__(self, settings):
|
|
self.test = settings.get('TEST', False)
|
|
self.header_image = settings.get('HEADER_IMAGE', None)
|
|
self.title = settings.get('TITLE', 'VPN Payment')
|
|
self.currency = settings.get('CURRENCY', 'EUR')
|
|
self.account_address = settings.get('ADDRESS')
|
|
self.receiver_address = settings.get('RECEIVER', self.account_address)
|
|
|
|
if self.test:
|
|
default_api = 'https://www.sandbox.paypal.com/'
|
|
else:
|
|
default_api = 'https://www.paypal.com/'
|
|
self.api_base = settings.get('API_BASE', default_api)
|
|
|
|
if self.account_address:
|
|
self.backend_enabled = True
|
|
|
|
def new_payment(self, payment):
|
|
ROOT_URL = project_settings.ROOT_URL
|
|
params = {
|
|
'cmd': '_xclick',
|
|
'notify_url': ROOT_URL + reverse('payments:cb_paypal', args=(payment.id,)),
|
|
'item_name': self.title,
|
|
'amount': '%.2f' % (payment.amount / 100),
|
|
'currency_code': self.currency,
|
|
'business': self.account_address,
|
|
'no_shipping': '1',
|
|
'return': ROOT_URL + reverse('payments:view', args=(payment.id,)),
|
|
'cancel_return': ROOT_URL + reverse('payments:cancel', args=(payment.id,)),
|
|
}
|
|
|
|
if self.header_image:
|
|
params['cpp_header_image'] = self.header_image
|
|
|
|
payment.status_message = _("Waiting for PayPal to confirm the transaction... " +
|
|
"It can take up to a few minutes...")
|
|
payment.save()
|
|
|
|
return redirect(self.api_base + '/cgi-bin/webscr?' + urlencode(params))
|
|
|
|
def new_subscription(self, rps):
|
|
months = {
|
|
'3m': 3,
|
|
'6m': 6,
|
|
'12m': 12,
|
|
}[rps.period]
|
|
|
|
ROOT_URL = project_settings.ROOT_URL
|
|
params = {
|
|
'cmd': '_xclick-subscriptions',
|
|
'notify_url': ROOT_URL + reverse('payments:cb_paypal_subscr', args=(rps.id,)),
|
|
'item_name': self.title,
|
|
'currency_code': self.currency,
|
|
'business': self.account_address,
|
|
'no_shipping': '1',
|
|
'return': ROOT_URL + reverse('payments:return_subscr', args=(rps.id,)),
|
|
'cancel_return': ROOT_URL + reverse('account:index'),
|
|
|
|
'a3': '%.2f' % (rps.period_amount / 100),
|
|
'p3': str(months),
|
|
't3': 'M',
|
|
'src': '1',
|
|
}
|
|
|
|
if self.header_image:
|
|
params['cpp_header_image'] = self.header_image
|
|
|
|
rps.save()
|
|
|
|
return redirect(self.api_base + '/cgi-bin/webscr?' + urlencode(params))
|
|
|
|
def handle_verified_callback(self, payment, params):
|
|
if self.test and params['test_ipn'] != '1':
|
|
raise ValueError('Test IPN')
|
|
|
|
txn_type = params.get('txn_type')
|
|
if txn_type not in (None, 'web_accept', 'express_checkout'):
|
|
# Not handled here and can be ignored
|
|
return
|
|
|
|
if params['payment_status'] == 'Refunded':
|
|
payment.status = 'refunded'
|
|
payment.status_message = None
|
|
|
|
elif params['payment_status'] == 'Completed':
|
|
self.handle_completed_payment(payment, params)
|
|
|
|
def handle_verified_callback_subscr(self, subscr, params):
|
|
if self.test and params['test_ipn'] != '1':
|
|
raise ValueError('Test IPN')
|
|
|
|
txn_type = params.get('txn_type')
|
|
if not txn_type.startswith('subscr_'):
|
|
# Not handled here and can be ignored
|
|
return
|
|
|
|
if txn_type == 'subscr_payment':
|
|
if params['payment_status'] == 'Refunded':
|
|
# FIXME: Find the payment and do something
|
|
pass
|
|
|
|
elif params['payment_status'] == 'Completed':
|
|
payment = subscr.create_payment()
|
|
if not self.handle_completed_payment(payment, params):
|
|
return
|
|
|
|
subscr.last_confirmed_payment = payment.created
|
|
subscr.backend_extid = params.get('subscr_id', '')
|
|
if subscr.status == 'new' or subscr.status == 'unconfirmed':
|
|
subscr.status = 'active'
|
|
subscr.save()
|
|
elif txn_type == 'subscr_cancel' or txn_type == 'subscr_eot':
|
|
subscr.status = 'cancelled'
|
|
subscr.save()
|
|
|
|
def handle_completed_payment(self, payment, params):
|
|
from payments.models import Payment
|
|
|
|
# Prevent making duplicate Payments if IPN is received twice
|
|
pc = Payment.objects.filter(backend_extid=params['txn_id']).count()
|
|
if pc > 0:
|
|
return False
|
|
|
|
if self.receiver_address != params['receiver_email']:
|
|
raise ValueError('Wrong receiver: ' + params['receiver_email'])
|
|
if self.currency.lower() != params['mc_currency'].lower():
|
|
raise ValueError('Wrong currency: ' + params['mc_currency'])
|
|
|
|
payment.paid_amount = int(float(params['mc_gross']) * 100)
|
|
if payment.paid_amount < payment.amount:
|
|
raise ValueError('Not fully paid.')
|
|
|
|
payment.user.vpnuser.add_paid_time(payment.time)
|
|
payment.user.vpnuser.on_payment_confirmed(payment)
|
|
payment.user.vpnuser.save()
|
|
|
|
payment.backend_extid = params['txn_id']
|
|
payment.status = 'confirmed'
|
|
payment.status_message = None
|
|
payment.save()
|
|
return True
|
|
|
|
def verify_ipn(self, request):
|
|
v_url = self.api_base + '/cgi-bin/webscr?cmd=_notify-validate'
|
|
v_req = urlopen(v_url, data=request.body, timeout=5)
|
|
v_res = v_req.read()
|
|
return v_res == b'VERIFIED'
|
|
|
|
def callback(self, payment, request):
|
|
if not self.verify_ipn(request):
|
|
return False
|
|
|
|
params = request.POST
|
|
|
|
try:
|
|
self.handle_verified_callback(payment, params)
|
|
return True
|
|
except (KeyError, ValueError) as e:
|
|
payment.status = 'error'
|
|
payment.status_message = None
|
|
payment.backend_data['ipn_exception'] = repr(e)
|
|
payment.backend_data['ipn_last_data'] = repr(request.POST)
|
|
payment.save()
|
|
raise
|
|
|
|
def callback_subscr(self, subscr, request):
|
|
if not self.verify_ipn(request):
|
|
return False
|
|
|
|
params = request.POST
|
|
|
|
try:
|
|
self.handle_verified_callback_subscr(subscr, params)
|
|
return True
|
|
except (KeyError, ValueError) as e:
|
|
subscr.status = 'error'
|
|
subscr.status_message = None
|
|
subscr.backend_data['ipn_exception'] = repr(e)
|
|
subscr.backend_data['ipn_last_data'] = repr(request.POST)
|
|
subscr.save()
|
|
raise
|
|
|
|
def get_ext_url(self, payment):
|
|
if not payment.backend_extid:
|
|
return None
|
|
url = 'https://history.paypal.com/webscr?cmd=_history-details-from-hub&id=%s'
|
|
return url % payment.backend_extid
|
|
|
|
def get_subscr_ext_url(self, subscr):
|
|
if not subscr.backend_extid:
|
|
return None
|
|
return ('https://www.paypal.com/fr/cgi-bin/webscr?cmd=_profile-recurring-payments'
|
|
'&encrypted_profile_id=%s' % subscr.backend_extid)
|
|
|