CCrypto VPN public website https://vpn.ccrypto.org/
You can not 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

  1. from django.shortcuts import redirect
  2. from django.utils.translation import ugettext_lazy as _
  3. from urllib.parse import urlencode
  4. from urllib.request import urlopen
  5. from django.core.urlresolvers import reverse
  6. from django.conf import settings as project_settings
  7. from .base import BackendBase
  8. class PaypalBackend(BackendBase):
  9. backend_id = 'paypal'
  10. backend_verbose_name = _("PayPal")
  11. backend_display_name = _("PayPal")
  12. backend_has_recurring = True
  13. def __init__(self, settings):
  14. self.test = settings.get('TEST', False)
  15. self.header_image = settings.get('HEADER_IMAGE', None)
  16. self.title = settings.get('TITLE', 'VPN Payment')
  17. self.currency = settings.get('CURRENCY', 'EUR')
  18. self.account_address = settings.get('ADDRESS')
  19. self.receiver_address = settings.get('RECEIVER', self.account_address)
  20. if self.test:
  21. default_api = 'https://www.sandbox.paypal.com/'
  22. else:
  23. default_api = 'https://www.paypal.com/'
  24. self.api_base = settings.get('API_BASE', default_api)
  25. if self.account_address:
  26. self.backend_enabled = True
  27. def new_payment(self, payment):
  28. ROOT_URL = project_settings.ROOT_URL
  29. params = {
  30. 'cmd': '_xclick',
  31. 'notify_url': ROOT_URL + reverse('payments:cb_paypal', args=(payment.id,)),
  32. 'item_name': self.title,
  33. 'amount': '%.2f' % (payment.amount / 100),
  34. 'currency_code': self.currency,
  35. 'business': self.account_address,
  36. 'no_shipping': '1',
  37. 'return': ROOT_URL + reverse('payments:view', args=(payment.id,)),
  38. 'cancel_return': ROOT_URL + reverse('payments:cancel', args=(payment.id,)),
  39. }
  40. if self.header_image:
  41. params['cpp_header_image'] = self.header_image
  42. payment.status_message = _("Waiting for PayPal to confirm the transaction... " +
  43. "It can take up to a few minutes...")
  44. payment.save()
  45. return redirect(self.api_base + '/cgi-bin/webscr?' + urlencode(params))
  46. def new_subscription(self, rps):
  47. months = {
  48. '3m': 3,
  49. '6m': 6,
  50. '12m': 12,
  51. }[rps.period]
  52. ROOT_URL = project_settings.ROOT_URL
  53. params = {
  54. 'cmd': '_xclick-subscriptions',
  55. 'notify_url': ROOT_URL + reverse('payments:cb_paypal_subscr', args=(rps.id,)),
  56. 'item_name': self.title,
  57. 'currency_code': self.currency,
  58. 'business': self.account_address,
  59. 'no_shipping': '1',
  60. 'return': ROOT_URL + reverse('payments:return_subscr', args=(rps.id,)),
  61. 'cancel_return': ROOT_URL + reverse('account:index'),
  62. 'a3': '%.2f' % (rps.period_amount / 100),
  63. 'p3': str(months),
  64. 't3': 'M',
  65. 'src': '1',
  66. }
  67. if self.header_image:
  68. params['cpp_header_image'] = self.header_image
  69. rps.save()
  70. return redirect(self.api_base + '/cgi-bin/webscr?' + urlencode(params))
  71. def handle_verified_callback(self, payment, params):
  72. if self.test and params['test_ipn'] != '1':
  73. raise ValueError('Test IPN')
  74. txn_type = params.get('txn_type')
  75. if txn_type not in (None, 'web_accept', 'express_checkout'):
  76. # Not handled here and can be ignored
  77. return
  78. if params['payment_status'] == 'Refunded':
  79. payment.status = 'refunded'
  80. payment.status_message = None
  81. elif params['payment_status'] == 'Completed':
  82. self.handle_completed_payment(payment, params)
  83. def handle_verified_callback_subscr(self, subscr, params):
  84. if self.test and params['test_ipn'] != '1':
  85. raise ValueError('Test IPN')
  86. txn_type = params.get('txn_type')
  87. if not txn_type.startswith('subscr_'):
  88. # Not handled here and can be ignored
  89. return
  90. if txn_type == 'subscr_payment':
  91. if params['payment_status'] == 'Refunded':
  92. # FIXME: Find the payment and do something
  93. pass
  94. elif params['payment_status'] == 'Completed':
  95. payment = subscr.create_payment()
  96. if not self.handle_completed_payment(payment, params):
  97. return
  98. subscr.last_confirmed_payment = payment.created
  99. subscr.backend_extid = params.get('subscr_id', '')
  100. if subscr.status == 'new' or subscr.status == 'unconfirmed':
  101. subscr.status = 'active'
  102. subscr.save()
  103. elif txn_type == 'subscr_cancel' or txn_type == 'subscr_eot':
  104. subscr.status = 'cancelled'
  105. subscr.save()
  106. def handle_completed_payment(self, payment, params):
  107. from payments.models import Payment
  108. # Prevent making duplicate Payments if IPN is received twice
  109. pc = Payment.objects.filter(backend_extid=params['txn_id']).count()
  110. if pc > 0:
  111. return False
  112. if self.receiver_address != params['receiver_email']:
  113. raise ValueError('Wrong receiver: ' + params['receiver_email'])
  114. if self.currency.lower() != params['mc_currency'].lower():
  115. raise ValueError('Wrong currency: ' + params['mc_currency'])
  116. payment.paid_amount = int(float(params['mc_gross']) * 100)
  117. if payment.paid_amount < payment.amount:
  118. raise ValueError('Not fully paid.')
  119. payment.user.vpnuser.add_paid_time(payment.time)
  120. payment.user.vpnuser.on_payment_confirmed(payment)
  121. payment.user.vpnuser.save()
  122. payment.backend_extid = params['txn_id']
  123. payment.status = 'confirmed'
  124. payment.status_message = None
  125. payment.save()
  126. return True
  127. def verify_ipn(self, request):
  128. v_url = self.api_base + '/cgi-bin/webscr?cmd=_notify-validate'
  129. v_req = urlopen(v_url, data=request.body, timeout=5)
  130. v_res = v_req.read()
  131. return v_res == b'VERIFIED'
  132. def callback(self, payment, request):
  133. if not self.verify_ipn(request):
  134. return False
  135. params = request.POST
  136. try:
  137. self.handle_verified_callback(payment, params)
  138. return True
  139. except (KeyError, ValueError) as e:
  140. payment.status = 'error'
  141. payment.status_message = None
  142. payment.backend_data['ipn_exception'] = repr(e)
  143. payment.backend_data['ipn_last_data'] = repr(request.POST)
  144. payment.save()
  145. raise
  146. def callback_subscr(self, subscr, request):
  147. if not self.verify_ipn(request):
  148. return False
  149. params = request.POST
  150. try:
  151. self.handle_verified_callback_subscr(subscr, params)
  152. return True
  153. except (KeyError, ValueError) as e:
  154. subscr.status = 'error'
  155. subscr.status_message = None
  156. subscr.backend_data['ipn_exception'] = repr(e)
  157. subscr.backend_data['ipn_last_data'] = repr(request.POST)
  158. subscr.save()
  159. raise
  160. def get_ext_url(self, payment):
  161. if not payment.backend_extid:
  162. return None
  163. url = 'https://history.paypal.com/webscr?cmd=_history-details-from-hub&id=%s'
  164. return url % payment.backend_extid
  165. def get_subscr_ext_url(self, subscr):
  166. if not subscr.backend_extid:
  167. return None
  168. return ('https://www.paypal.com/fr/cgi-bin/webscr?cmd=_profile-recurring-payments'
  169. '&encrypted_profile_id=%s' % subscr.backend_extid)