fix race in filters
[wolnelektury.git] / src / paypal / rest.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from datetime import date, datetime, timedelta
5 from decimal import Decimal
6 import paypalrestsdk
7 from django.contrib.sites.models import Site
8 from django.urls import reverse
9 from django.utils import timezone
10 from django.conf import settings
11 from .models import BillingPlan, BillingAgreement
12
13 paypalrestsdk.configure(settings.PAYPAL_CONFIG)
14
15
16 class PaypalError(Exception):
17     pass
18
19
20 def absolute_url(url_name, kwargs=None):
21     return "http://%s%s" % (Site.objects.get_current().domain, reverse(url_name, kwargs=kwargs))
22
23
24 def create_plan(amount):
25     billing_plan = paypalrestsdk.BillingPlan({
26         "name": "Cykliczna darowizna na Wolne Lektury: %s zł" % amount,
27         "description": "Cykliczna darowizna na wsparcie Wolnych Lektur",
28         "merchant_preferences": {
29             "auto_bill_amount": "yes",
30             "return_url": absolute_url('paypal_return', {'key': '-'}),
31             "cancel_url": absolute_url('paypal_cancel'),
32             # "initial_fail_amount_action": "continue",
33             "max_fail_attempts": "3",
34         },
35         "payment_definitions": [
36             {
37                 "amount": {
38                     "currency": "PLN",
39                     "value": str(amount),
40                 },
41                 "cycles": "0",
42                 "frequency": "MONTH",
43                 "frequency_interval": "1",
44                 "name": "Cykliczna darowizna",
45                 "type": "REGULAR",
46             }
47         ],
48         "type": "INFINITE",
49     })
50
51     if not billing_plan.create():
52         raise PaypalError(billing_plan.error)
53     if not billing_plan.activate():
54         raise PaypalError(billing_plan.error)
55     plan, created = BillingPlan.objects.get_or_create(amount=amount, defaults={'plan_id': billing_plan.id})
56     return plan.plan_id
57
58
59 def get_link(links, rel):
60     for link in links:
61         if link.rel == rel:
62             return link.href
63
64
65 def create_agreement(amount, key, app=False):
66     try:
67         plan = BillingPlan.objects.get(amount=amount)
68     except BillingPlan.DoesNotExist:
69         plan_id = create_plan(amount)
70     else:
71         plan_id = plan.plan_id
72     start = (timezone.now() + timedelta(0, 3600*24)).astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
73     billing_agreement = paypalrestsdk.BillingAgreement({
74         "name": "Subskrypcja klubu WL",
75         "description": "Stałe wsparcie Wolnych Lektur kwotą %s złotych" % amount,
76         "start_date": start,
77         "plan": {
78             "id": plan_id,
79         },
80         "payer": {
81             "payment_method": "paypal"
82         },
83     })
84     if app:
85         billing_agreement['override_merchant_preferences'] = {
86             'return_url': absolute_url('paypal_app_return', {'key': key}),
87         }
88     else:
89         billing_agreement['override_merchant_preferences'] = {
90             'return_url': absolute_url('paypal_return', {'key': key}),
91         }
92
93
94     response = billing_agreement.create()
95     if response:
96         return billing_agreement
97     else:
98         raise PaypalError(billing_agreement.error)
99
100
101 def agreement_approval_url(amount, key, app=False):
102     agreement = create_agreement(amount, key, app=app)
103     return get_link(agreement.links, 'approval_url')
104
105
106 def get_agreement(agreement_id):
107     try:
108         return paypalrestsdk.BillingAgreement.find(agreement_id)
109     except paypalrestsdk.ResourceNotFound:
110         return None
111
112
113 def check_agreement(agreement_id):
114     a = get_agreement(agreement_id)
115     if a:
116         return a.state == 'Active'
117
118
119 def execute_agreement(token):
120     return paypalrestsdk.BillingAgreement.execute(token)
121
122
123 def get_donations(agreement_id, year):
124     a = get_agreement(agreement_id)
125     transactions = []
126     for transaction in a.search_transactions(
127             date(year - 1, 12, 31),
128             date(year + 1, 1, 1))['agreement_transaction_list']:
129         if transaction['status'] != 'Completed':
130             continue
131         dt = datetime.strptime(
132             transaction['time_stamp'],
133             '%Y-%m-%dT%H:%M:%S%z'
134         ).astimezone()
135         if dt.year != year:
136             continue
137         assert transaction['amount']['currency'] == 'PLN'
138         transactions.append({
139             'timestamp': dt,
140             'amount': Decimal(transaction['amount']['value'])
141         })
142     return transactions