#!/usr/bin/python3
import iso3166
-import tornado.gen
import tornado.web
from . import base
-class DonateHandler(base.BaseHandler):
- @base.blacklisted
- def prepare(self):
- # Makes sure that we call blacklist for everything
- pass
+SKUS = {
+ "monthly" : "IPFIRE-DONATION-MONTHLY",
+ "quarterly" : "IPFIRE-DONATION-QUARTERLY",
+ "yearly" : "IPFIRE-DONATION-YEARLY",
+}
+DEFAULT_SKU = "IPFIRE-DONATION"
+class DonateHandler(base.BaseHandler):
def get(self):
- location = self.get_remote_location()
-
- if location:
- country = location.country
+ if self.current_user:
+ country = self.current_user.country_code
else:
- country = None
+ country = self.current_country_code
# Get defaults
- first_name = self.get_argument("first_name", None)
- last_name = self.get_argument("last_name", None)
- amount = self.get_argument_int("amount", None)
+ amount = self.get_argument_float("amount", None)
currency = self.get_argument("currency", None)
frequency = self.get_argument("frequency", None)
frequency = "one-time"
self.render("donate/donate.html", countries=iso3166.countries,
- country=country, first_name=first_name, last_name=last_name,
- amount=amount, currency=currency, frequency=frequency)
+ country=country, amount=amount, currency=currency, frequency=frequency)
+
+ @base.ratelimit(minutes=15, requests=5)
+ async def post(self):
+ type = self.get_argument("type")
+ if not type in ("individual", "organization"):
+ raise tornado.web.HTTPError(400, "type is of an invalid value: %s" % type)
- @tornado.gen.coroutine
- @base.ratelimit(minutes=24*60, requests=5)
- def post(self):
amount = self.get_argument("amount")
currency = self.get_argument("currency", "EUR")
frequency = self.get_argument("frequency")
- # Get form inputs
- args = {
- "amount" : amount,
- "currency" : currency,
+ organization = None
+ locale = self.get_browser_locale()
- # Is this a recurring donation?
- "recurring" : frequency == "monthly",
+ # Get organization information
+ if type == "organization":
+ organization = {
+ "name" : self.get_argument("organization"),
+ "vat_number" : self.get_argument("vat_number", None),
+ }
- # Address
+ # Collect person information
+ person = {
"email" : self.get_argument("email"),
"title" : self.get_argument("title"),
"first_name" : self.get_argument("first_name"),
"last_name" : self.get_argument("last_name"),
- "company_name" : self.get_argument("company_name", None),
+ "locale" : locale.code,
+ }
+
+ # Collect address information
+ address = {
"street1" : self.get_argument("street1"),
"street2" : self.get_argument("street2", None),
"post_code" : self.get_argument("post_code"),
"country_code" : self.get_argument("country_code"),
}
- # Add URLs to redirect the user back
- args.update({
- "success_url" : "https://%s/donate/thank-you" % self.request.host,
- "error_url" : "https://%s/donate/error" % self.request.host,
- "back_url" : "https://%s/donate?amount=%s¤cy=%s&frequency=%s" %
- (self.request.host, amount, currency, frequency),
- })
-
- # Send request to Zeiterfassung
+ # Send everything to Zeiterfassung
try:
- response = yield self.backend.zeiterfassung.send_request(
- "/api/v1/donations/create/ipfire-project", **args)
+ # Create a new organization
+ if organization:
+ organization = await self._create_organization(organization, address)
+
+ # Create a person
+ person = await self._create_person(person, address, organization)
+
+ # Create a new order
+ order = await self._create_order(person=person, currency=currency)
+
+ # Add donation to the order
+ await self._create_donation(order, frequency, amount, currency,
+ vat_included=(type == "individual"))
+ # Submit the order
+ needs_payment = await self._submit_order(order)
+
+ # Pay the order
+ if needs_payment:
+ redirect_url = await self._pay_order(order)
+ else:
+ redirect_url = "https://%s/donate/thank-you" % self.request.host
+
+ # Redirect the user to the payment page
+ if not redirect_url:
+ raise tornado.web.HTTPError(500, "Did not receive a redirect URL")
+
+ self.redirect(redirect_url)
+
+ # XXX handle any problems when Zeiterfassung is unreachable
except Exception:
- raise # XXX handle any problems when Zeiterfassung is unreachable
+ raise
+
+ async def _create_organization(self, organization, address):
+ # Check if we have an existing organization
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/organizations/search", **organization,
+ )
+
+ # Update details if we found a match
+ if response:
+ number = response.get("number")
+
+ # Update name
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/organizations/%s/name" % number, **organization
+ )
+
+ # Update VAT number
+ vat_number = organization.get("vat_number", None)
+ if vat_number:
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/organizations/%s/vat-number" % number, vat_number=vat_number,
+ )
+
+ # Update address
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/organizations/%s/address" % number, **address,
+ )
+
+ return number
+
+ # Otherwise we will create a new one
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/organizations/create", **organization, **address,
+ )
+
+ # Return the organization's number
+ return response.get("number")
+
+ async def _create_person(self, person, address, organization=None):
+ """
+ Searches for a matching person or creates a new one
+ """
+ # Check if we have an existing person
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/persons/search", **person
+ )
+
+ # Update details if we found a match
+ if response:
+ number = response.get("number")
+
+ # Update name
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/persons/%s/name" % number, **person,
+ )
+
+ # Update address
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/persons/%s/address" % number, **address,
+ )
+
+ return number
+
+ # If not, we will create a new one
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/persons/create", organization=organization, **person, **address
+ )
+
+ # Return the persons's number
+ return response.get("number")
+
+ async def _create_order(self, person, currency=None):
+ """
+ Creates a new order and returns its ID
+ """
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/orders/create", person=person, currency=currency,
+ )
+
+ # Return the order number
+ return response.get("number")
+
+ async def _create_donation(self, order, frequency, amount, currency,
+ vat_included=False):
+ """
+ Creates a new donation
+ """
+ # Select the correct product
+ try:
+ sku = SKUS[frequency]
+ except KeyError:
+ sku = DEFAULT_SKU
+
+ # Add it to the order
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/orders/%s/products/add" % order, sku=sku, quantity=1,
+ )
+
+ # Set the price
+ await self.backend.zeiterfassung.send_request(
+ "/api/v1/orders/%s/products/%s/price" % (order, sku),
+ price=amount, currency=currency, vat_included=vat_included,
+ )
+
+ async def _submit_order(self, order):
+ """
+ Submits the order
+ """
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/orders/%s/submit" % order,
+ )
+
+ # Return whether this needs payment
+ return not response.get("is_authorized")
+
+ async def _pay_order(self, order):
+ """
+ Pay the order
+ """
+ # Add URLs to redirect the user back
+ urls = {
+ "success_url" : "https://%s/donate/thank-you" % self.request.host,
+ "error_url" : "https://%s/donate/error" % self.request.host,
+ "back_url" : "https://%s/donate" % self.request.host,
+ }
- # Redirect the user to the payment page
- redirect_url = response.get("redirect_url")
- if not redirect_url:
- raise tornado.web.HTTPError(500, "Did not receive a redirect URL")
+ # Send request
+ response = await self.backend.zeiterfassung.send_request(
+ "/api/v1/orders/%s/pay" % order, **urls,
+ )
- self.redirect(redirect_url)
+ # Return redirect URL
+ return response.get("redirect_url", None)
class ThankYouHandler(base.BaseHandler):