]> git.ipfire.org Git - ipfire.org.git/blame - src/web/donate.py
donate: Implement a basic VAT number check
[ipfire.org.git] / src / web / donate.py
CommitLineData
c7bcb9ca
MT
1#!/usr/bin/python3
2
3import iso3166
c7bcb9ca
MT
4import tornado.web
5
6from . import base
7
b577aabf
MT
8SKUS = {
9 "monthly" : "IPFIRE-DONATION-MONTHLY",
10 "quarterly" : "IPFIRE-DONATION-QUARTERLY",
11 "yearly" : "IPFIRE-DONATION-YEARLY",
12}
13DEFAULT_SKU = "IPFIRE-DONATION"
14
28e09035 15class DonateHandler(base.AnalyticsMixin, base.BaseHandler):
c7bcb9ca 16 def get(self):
1898a8bb
MT
17 if self.current_user:
18 country = self.current_user.country_code
19 else:
20 country = self.current_country_code
c7bcb9ca
MT
21
22 # Get defaults
a5f94966 23 amount = self.get_argument_float("amount", None)
c7bcb9ca
MT
24 currency = self.get_argument("currency", None)
25 frequency = self.get_argument("frequency", None)
26
27 # Set default currency
28 if not currency in ("EUR", "USD"):
29 currency = "EUR"
30
31 # Default to USD for the US only
32 if country == "US":
33 currency = "USD"
34
35 # Set default frequency
36 if not frequency in ("one-time", "monthly"):
37 frequency = "one-time"
38
0ba47b0f 39 self.render("donate/donate.html", countries=iso3166.countries,
1898a8bb 40 country=country, amount=amount, currency=currency, frequency=frequency)
c7bcb9ca 41
53a15fe0 42 @base.ratelimit(minutes=15, requests=5)
9fdf4fb7 43 async def post(self):
9e5eac7d
MT
44 type = self.get_argument("type")
45 if not type in ("individual", "organization"):
46 raise tornado.web.HTTPError(400, "type is of an invalid value: %s" % type)
47
c7bcb9ca
MT
48 amount = self.get_argument("amount")
49 currency = self.get_argument("currency", "EUR")
50 frequency = self.get_argument("frequency")
51
9e5eac7d 52 organization = None
21f7174c 53 locale = self.get_browser_locale()
9e5eac7d
MT
54
55 # Get organization information
56 if type == "organization":
57 organization = {
58 "name" : self.get_argument("organization"),
59 "vat_number" : self.get_argument("vat_number", None),
60 }
61
62 # Collect person information
63 person = {
c7bcb9ca
MT
64 "email" : self.get_argument("email"),
65 "title" : self.get_argument("title"),
66 "first_name" : self.get_argument("first_name"),
67 "last_name" : self.get_argument("last_name"),
21f7174c 68 "locale" : locale.code,
bd9cc41d
MT
69 }
70
bd9cc41d
MT
71 # Collect address information
72 address = {
c7bcb9ca
MT
73 "street1" : self.get_argument("street1"),
74 "street2" : self.get_argument("street2", None),
75 "post_code" : self.get_argument("post_code"),
76 "city" : self.get_argument("city"),
77 "state" : self.get_argument("state", None),
78 "country_code" : self.get_argument("country_code"),
79 }
80
bd9cc41d 81 # Send everything to Zeiterfassung
c7bcb9ca 82 try:
9e5eac7d
MT
83 # Create a new organization
84 if organization:
85 organization = await self._create_organization(organization, address)
c7bcb9ca 86
9e5eac7d
MT
87 # Create a person
88 person = await self._create_person(person, address, organization)
c7bcb9ca 89
b577aabf
MT
90 # Create a new order
91 order = await self._create_order(person=person, currency=currency)
bd9cc41d 92
b577aabf 93 # Add donation to the order
9e5eac7d
MT
94 await self._create_donation(order, frequency, amount, currency,
95 vat_included=(type == "individual"))
bd9cc41d 96
b577aabf
MT
97 # Submit the order
98 needs_payment = await self._submit_order(order)
bd9cc41d 99
b577aabf
MT
100 # Pay the order
101 if needs_payment:
102 redirect_url = await self._pay_order(order)
103 else:
104 redirect_url = "https://%s/donate/thank-you" % self.request.host
bd9cc41d
MT
105
106 # Redirect the user to the payment page
bd9cc41d
MT
107 if not redirect_url:
108 raise tornado.web.HTTPError(500, "Did not receive a redirect URL")
109
110 self.redirect(redirect_url)
111
112 # XXX handle any problems when Zeiterfassung is unreachable
113 except Exception:
114 raise
c7bcb9ca 115
9e5eac7d
MT
116 async def _create_organization(self, organization, address):
117 # Check if we have an existing organization
118 response = await self.backend.zeiterfassung.send_request(
119 "/api/v1/organizations/search", **organization,
120 )
121
122 # Update details if we found a match
123 if response:
124 number = response.get("number")
125
126 # Update name
127 await self.backend.zeiterfassung.send_request(
128 "/api/v1/organizations/%s/name" % number, **organization
129 )
130
131 # Update VAT number
132 vat_number = organization.get("vat_number", None)
133 if vat_number:
134 await self.backend.zeiterfassung.send_request(
135 "/api/v1/organizations/%s/vat-number" % number, vat_number=vat_number,
136 )
137
138 # Update address
139 await self.backend.zeiterfassung.send_request(
140 "/api/v1/organizations/%s/address" % number, **address,
141 )
142
143 return number
144
145 # Otherwise we will create a new one
146 response = await self.backend.zeiterfassung.send_request(
147 "/api/v1/organizations/create", **organization, **address,
148 )
149
150 # Return the organization's number
151 return response.get("number")
152
153 async def _create_person(self, person, address, organization=None):
154 """
155 Searches for a matching person or creates a new one
156 """
157 # Check if we have an existing person
158 response = await self.backend.zeiterfassung.send_request(
159 "/api/v1/persons/search", **person
160 )
161
162 # Update details if we found a match
163 if response:
164 number = response.get("number")
165
166 # Update name
167 await self.backend.zeiterfassung.send_request(
168 "/api/v1/persons/%s/name" % number, **person,
169 )
170
171 # Update address
172 await self.backend.zeiterfassung.send_request(
173 "/api/v1/persons/%s/address" % number, **address,
174 )
175
176 return number
177
178 # If not, we will create a new one
179 response = await self.backend.zeiterfassung.send_request(
180 "/api/v1/persons/create", organization=organization, **person, **address
181 )
182
183 # Return the persons's number
184 return response.get("number")
185
b577aabf
MT
186 async def _create_order(self, person, currency=None):
187 """
188 Creates a new order and returns its ID
189 """
190 response = await self.backend.zeiterfassung.send_request(
191 "/api/v1/orders/create", person=person, currency=currency,
192 )
193
194 # Return the order number
195 return response.get("number")
196
9e5eac7d
MT
197 async def _create_donation(self, order, frequency, amount, currency,
198 vat_included=False):
b577aabf
MT
199 """
200 Creates a new donation
201 """
202 # Select the correct product
203 try:
204 sku = SKUS[frequency]
205 except KeyError:
206 sku = DEFAULT_SKU
207
208 # Add it to the order
209 await self.backend.zeiterfassung.send_request(
210 "/api/v1/orders/%s/products/add" % order, sku=sku, quantity=1,
211 )
212
213 # Set the price
214 await self.backend.zeiterfassung.send_request(
215 "/api/v1/orders/%s/products/%s/price" % (order, sku),
9e5eac7d 216 price=amount, currency=currency, vat_included=vat_included,
b577aabf
MT
217 )
218
219 async def _submit_order(self, order):
220 """
221 Submits the order
222 """
223 response = await self.backend.zeiterfassung.send_request(
224 "/api/v1/orders/%s/submit" % order,
225 )
226
227 # Return whether this needs payment
228 return not response.get("is_authorized")
229
230 async def _pay_order(self, order):
231 """
232 Pay the order
233 """
234 # Add URLs to redirect the user back
235 urls = {
236 "success_url" : "https://%s/donate/thank-you" % self.request.host,
237 "error_url" : "https://%s/donate/error" % self.request.host,
238 "back_url" : "https://%s/donate" % self.request.host,
239 }
240
241 # Send request
242 response = await self.backend.zeiterfassung.send_request(
243 "/api/v1/orders/%s/pay" % order, **urls,
244 )
245
246 # Return redirect URL
247 return response.get("redirect_url", None)
248
c7bcb9ca
MT
249
250class ThankYouHandler(base.BaseHandler):
251 def get(self):
252 self.render("donate/thank-you.html")
253
254
255class ErrorHandler(base.BaseHandler):
256 def get(self):
257 self.render("donate/error.html")
d37d6cd2
MT
258
259
260class CheckVATNumberHandler(base.APIHandler):
261 @base.ratelimit(minutes=5, requests=25)
262 async def get(self):
263 vat_number = self.get_argument("vat_number")
264
265 # Send request
266 response = await self.backend.zeiterfassung.send_request(
267 "/api/v1/organizations/check-vat-number", vat_number=vat_number)
268
269 # Forward the response
270 self.finish(response)