]>
Commit | Line | Data |
---|---|---|
c7bcb9ca MT |
1 | #!/usr/bin/python3 |
2 | ||
3 | import iso3166 | |
c7bcb9ca MT |
4 | import tornado.web |
5 | ||
6 | from . import base | |
7 | ||
b577aabf MT |
8 | SKUS = { |
9 | "monthly" : "IPFIRE-DONATION-MONTHLY", | |
10 | "quarterly" : "IPFIRE-DONATION-QUARTERLY", | |
11 | "yearly" : "IPFIRE-DONATION-YEARLY", | |
12 | } | |
13 | DEFAULT_SKU = "IPFIRE-DONATION" | |
14 | ||
28e09035 | 15 | class 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 | |
250 | class ThankYouHandler(base.BaseHandler): | |
251 | def get(self): | |
252 | self.render("donate/thank-you.html") | |
253 | ||
254 | ||
255 | class ErrorHandler(base.BaseHandler): | |
256 | def get(self): | |
257 | self.render("donate/error.html") |