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