diff --git a/config.py b/config.py index cecbc97..b951830 100644 --- a/config.py +++ b/config.py @@ -1,8 +1,11 @@ import configparser from etcd_wrapper import EtcdWrapper - +from ldap_manager import LdapManager config = configparser.ConfigParser() config.read('pay.conf') etcd_client = EtcdWrapper(host=config['etcd']['host'], port=config['etcd']['port']) + +ldap_manager = LdapManager(server=config['ldap']['server'], admin_dn=config['ldap']['admin_dn'], + admin_password=config['ldap']['admin_password']) \ No newline at end of file diff --git a/helper.py b/helper.py index c2000f5..d1a5dd4 100644 --- a/helper.py +++ b/helper.py @@ -1,26 +1,14 @@ -import config -from stripe_utils import StripeUtils +import logging -etcd_client = config.etcd_client +from stripe_utils import StripeUtils def get_plan_id_from_product(product): plan_id = 'ucloud-v1-' plan_id += product['name'].strip().replace(' ', '-') - # plan_id += '-' + product['type'] return plan_id -def get_order_id(): - order_id_kv = etcd_client.get('/v1/last_order_id') - if order_id_kv is not None: - order_id = int(order_id_kv.value) + 1 - else: - order_id = 0 - etcd_client.put('/v1/last_order_id', str(order_id)) - return 'OR-{}'.format(order_id) - - def get_pricing(price_in_chf_cents, product_type, recurring_period): if product_type == 'recurring': return 'CHF {}/{}'.format(price_in_chf_cents/100, recurring_period) @@ -53,10 +41,26 @@ def get_token(card_number, cvc, exp_month, exp_year): return None -def resolve_product_usable_id(usable_id, etcd_client): +def resolve_product(usable_id, etcd_client): products = etcd_client.get_prefix('/v1/products/', value_in_json=True) for p in products: if p.value['usable-id'] == usable_id: - print(p.value['uuid'], usable_id) - return p.value['uuid'] + return p.value return None + + +def calculate_charges(specification, data): + logging.debug('Calculating charges for specs:{} and data:{}'.format(specification, data)) + one_time_charge = 0 + recurring_charge = 0 + for feature_name, feature_detail in specification['features'].items(): + if feature_detail['constant']: + data[feature_name] = 1 + + if feature_detail['unit']['type'] != 'str': + one_time_charge += feature_detail['one_time_fee'] + recurring_charge += ( + feature_detail['price_per_unit_per_period'] * data[feature_name] / + feature_detail['unit']['value'] + ) + return one_time_charge, recurring_charge diff --git a/ldap_manager.py b/ldap_manager.py index f8cfaa3..382afab 100644 --- a/ldap_manager.py +++ b/ldap_manager.py @@ -22,12 +22,17 @@ class LdapManager: r = Reader(**kwargs) return r.search() - def is_password_valid(self, email, password, **kwargs): - entries = self.get(query='(mail={})'.format(email), **kwargs) + def is_password_valid(self, query_value, password, query_key='mail', **kwargs): + entries = self.get(query='({}={})'.format(query_key, query_value), **kwargs) if entries: password_in_ldap = entries[0].userPassword.value - return self._check_password(password_in_ldap, password) - return False + found = self._check_password(password_in_ldap, password) + if not found: + raise Exception('Invalid Password') + else: + return entries[0] + else: + raise ValueError('Such {}={} not found'.format(query_key, query_value)) @staticmethod def _check_password(tagged_digest_salt, password): diff --git a/products/ipv6-only-django.json b/products/ipv6-only-django.json index b3d8730..110027a 100644 --- a/products/ipv6-only-django.json +++ b/products/ipv6-only-django.json @@ -4,6 +4,7 @@ "name": "IPv6 Only Django Hosting", "description": "Host your Django application on our shiny IPv6 Only VM", "recurring_period": "month", + "quantity": "inf", "features": { "cpu": { "unit": {"value": 1, "type":"int"}, diff --git a/products/ipv6-only-vm.json b/products/ipv6-only-vm.json index 6b21b26..d07ad6c 100644 --- a/products/ipv6-only-vm.json +++ b/products/ipv6-only-vm.json @@ -4,6 +4,7 @@ "name": "IPv6 Only VM", "description": "IPv6 Only VM are accessible to only those having IPv6 for themselves", "recurring_period": "month", + "quantity": "inf", "features": { "cpu": { "unit": {"value": 1, "type":"int"}, diff --git a/products/ipv6-only-vpn.json b/products/ipv6-only-vpn.json index 43ed7bd..38c6201 100644 --- a/products/ipv6-only-vpn.json +++ b/products/ipv6-only-vpn.json @@ -4,6 +4,7 @@ "name": "IPv6 Only VPN", "description": "IPv6 VPN enable you to access IPv6 only websites and more", "recurring_period": "month", + "quantity": "inf", "features": { "vpn": { "unit": {"value": 1, "type": "int"}, diff --git a/products/ipv6box.json b/products/ipv6box.json new file mode 100644 index 0000000..eca11f0 --- /dev/null +++ b/products/ipv6box.json @@ -0,0 +1,16 @@ +{ + "usable-id": "ipv6-box", + "active": true, + "name": "IPv6 Box", + "description": "A ready-to-go IPv6 Box: it creates a VPN to ungleich and distributes IPv6 addresses to all your computers.", + "recurring_period": "eternity", + "quantity": 4, + "features": { + "ipv6-box": { + "unit": {"value": 1, "type":"int"}, + "price_per_unit_per_period": 0, + "one_time_fee": 250, + "constant": true + } + } +} diff --git a/products/membership.json b/products/membership.json index 14596fa..4003330 100644 --- a/products/membership.json +++ b/products/membership.json @@ -3,13 +3,15 @@ "active": true, "name": "Membership", "description": "Membership to use uncloud-pay", - "recurring_period": "eternity", + "recurring_period": "month", + "quantity": "inf", "features": { "membership": { "unit": {"value": 1, "type":"int"}, - "price_per_unit_per_period": 0, - "one_time_fee": 5, + "price_per_unit_per_period": 5, + "one_time_fee": 0, "constant": true } - } + }, + "max_per_user": "1" } diff --git a/schemas.py b/schemas.py index 9d0c97f..c128d19 100644 --- a/schemas.py +++ b/schemas.py @@ -1,7 +1,10 @@ import logging import config +import json +import math -from helper import resolve_product_usable_id +from config import ldap_manager +from helper import resolve_product etcd_client = config.etcd_client @@ -11,26 +14,23 @@ class ValidationException(Exception): class Field: - def __init__(self, _name, _type, _value=None, validators=None): - if validators is None: - validators = [] - - assert isinstance(validators, list) - + def __init__(self, _name, _type, _value=None, validators=None, disable_validation=False): + self.validation_disabled = disable_validation self.name = _name self.value = _value self.type = _type - self.validators = validators + self.validators = validators or [] def is_valid(self): - if not isinstance(self.value, self.type): - try: - self.value = self.type(self.value) - except Exception: - raise ValidationException("Incorrect Type for '{}' field".format(self.name)) + if not self.validation_disabled: + if not isinstance(self.value, self.type): + try: + self.value = self.type(self.value) + except Exception: + raise ValidationException("Incorrect Type for '{}' field".format(self.name)) - for validator in self.validators: - validator() + for validator in self.validators: + validator() def __repr__(self): return self.name @@ -38,86 +38,171 @@ class Field: class BaseSchema: def __init__(self): - self.fields = [getattr(self, field) for field in dir(self) if isinstance(getattr(self, field), Field)] + self.objects = {} def validation(self): # custom validation is optional return True + def get_fields(self): + return [getattr(self, field) for field in dir(self) if isinstance(getattr(self, field), Field)] + def is_valid(self): - for field in self.fields: + for field in self.get_fields(): field.is_valid() - - for parent in self.__class__.__bases__: - parent.validation(self) - self.validation() - for field in self.fields: - setattr(self, field.name, field.value) - - def return_data(self): - return { + def get_cleaned_values(self): + field_kv_dict = { field.name: field.value - for field in self.fields + for field in self.get_fields() } + cleaned_values = field_kv_dict + cleaned_values.update(self.objects) + return cleaned_values -def get(dictionary: dict, key: str, return_default=False, default=None): - if dictionary is None: - raise ValidationException('No data provided at all.') - try: - value = dictionary[key] - except KeyError: - if return_default: - return default - raise ValidationException("Missing data for '{}' field.".format(key)) - else: - return value + def add_schema(self, schema, data, under_field_name=None): + s = schema(data) + s.is_valid() + + base = self + if under_field_name: + # Create a field in self + setattr(self, under_field_name, Field(under_field_name, dict, _value={}, disable_validation=True)) + base = getattr(self, under_field_name) + + for field in s.get_fields(): + if under_field_name: + getattr(base, 'value')[field.name] = field.value + else: + setattr(base, field.name, field) + + self.objects.update(s.objects) + + @staticmethod + def get(dictionary: dict, key: str, return_default=False, default=None): + if dictionary is None: + raise ValidationException('No data provided at all.') + try: + value = dictionary[key] + except KeyError: + if return_default: + return {'_value': default, 'disable_validation': True} + raise ValidationException("Missing data for '{}' field.".format(key)) + else: + return {'_value': value, 'disable_validation': False} class AddProductSchema(BaseSchema): def __init__(self, data): - self.email = Field('email', str, get(data, 'email')) - self.password = Field('password', str, get(data, 'password')) - self.specs = Field('specs', dict, get(data, 'specs')) super().__init__() + self.add_schema(UserCredentialSchema, data) + self.specs = Field('specs', dict, **self.get(data, 'specs')) + self.update = Field('update', bool, **self.get(data, 'update', return_default=True, default=False)) + + def validation(self): + user = self.objects['user'] + user = json.loads(user.entry_to_json()) + uid, ou, *dc = user['dn'].replace('ou=', '').replace('dc=', '').replace('uid=', '').split(',') + if ou != config.config['ldap']['internal_user_ou']: + raise ValidationException('You do not have access to create product.') + + product = resolve_product(self.specs.value['usable-id'], etcd_client) + if product: + self.objects['product'] = product + + +class AddressSchema(BaseSchema): + def __init__(self, data): + super().__init__() + self.line1 = Field('line1', str, **self.get(data, 'line1')) + self.line2 = Field('line2', str, **self.get(data, 'line2', return_default=True)) + self.city = Field('city', str, **self.get(data, 'city')) + self.country = Field('country', str, **self.get(data, 'country')) + self.state = Field('state', str, **self.get(data, 'state', return_default=True)) + self.postal_code = Field('postal_code', str, **self.get(data, 'postal_code', return_default=True)) class UserRegisterPaymentSchema(BaseSchema): def __init__(self, data): - self.email = Field('email', str, get(data, 'email')) - self.password = Field('password', str, get(data, 'password')) - self.card_number = Field('card_number', str, get(data, 'card_number')) - self.cvc = Field('cvc', str, get(data, 'cvc')) - self.expiry_year = Field('expiry_year', int, get(data, 'expiry_year')) - self.expiry_month = Field('expiry_month', int, get(data, 'expiry_month')) - self.card_holder_name = Field('card_holder_name', str, get(data, 'card_holder_name')) - super().__init__() + self.add_schema(UserCredentialSchema, data) + self.add_schema(AddressSchema, data, under_field_name='address') + + self.card_number = Field('card_number', str, **self.get(data, 'card_number')) + self.cvc = Field('cvc', str, **self.get(data, 'cvc')) + self.expiry_year = Field('expiry_year', int, **self.get(data, 'expiry_year')) + self.expiry_month = Field('expiry_month', int, **self.get(data, 'expiry_month')) + self.card_holder_name = Field('card_holder_name', str, **self.get(data, 'card_holder_name')) + + +class UserCredentialSchema(BaseSchema): + def __init__(self, data): + super().__init__() + self.username = Field('username', str, **self.get(data, 'username')) + self.password = Field('password', str, **self.get(data, 'password')) + + def validation(self): + try: + entry = ldap_manager.is_password_valid(self.username.value, self.password.value, query_key='uid') + except ValueError: + raise ValidationException('No user with \'{}\' username found. You can create account at ' + 'https://account.ungleich.ch'.format(self.username.value)) + except Exception: + raise ValidationException('Invalid username/password.') + else: + self.objects['user'] = entry + class ProductOrderSchema(BaseSchema): def __init__(self, data): - self.email = Field('email', str, get(data, 'email')) - self.password = Field('password', str, get(data, 'password')) - self.product_id = Field('product_id', str, get(data, 'product_id'), validators=[self.product_id_validation]) - super().__init__() + self.product_id = Field( + 'product_id', str, **self.get(data, 'product_id'), validators=[self.product_id_validation] + ) + self.pay_consent = Field('pay', bool, **self.get(data, 'pay', return_default=True, default=False)) + self.add_schema(UserCredentialSchema, data) def product_id_validation(self): - product_uuid = resolve_product_usable_id(self.product_id.value, etcd_client) - if product_uuid: - self.product_id.value = product_uuid + product = resolve_product(self.product_id.value, etcd_client) + if product: + self.product_id.value = product['uuid'] + self.objects['product'] = product + logging.debug('Got product {}'.format(product)) + + if not product['active']: + raise ValidationException('Product is not active at the moment.') + + if product['quantity'] <= 0: + raise ValidationException('Out of stock.') else: - raise ValidationException('Invalid Product ID') + raise ValidationException('No such product exists.') + + def validation(self): + customer_previous_orders = etcd_client.get_prefix('/v1/user/{}'.format(self.username.value), value_in_json=True) + customer_previous_orders = [o.value for o in customer_previous_orders] + membership = next(filter(lambda o: o['product'] == 'membership', customer_previous_orders), None) + if membership is None and self.objects['product']['usable-id'] != 'membership': + raise ValidationException('Please buy membership first to use this facility') + max_quantity_user_can_order = float(self.objects['product'].get('max_per_user', math.inf)) + previous_order_of_same_product = [ + o for o in customer_previous_orders if o['product'] == self.objects['product']['usable-id'] + ] + if len(previous_order_of_same_product) >= max_quantity_user_can_order: + raise ValidationException( + 'You cannot buy {} more than {} times'.format( + self.objects['product']['name'], int(max_quantity_user_can_order) + ) + ) class OrderListSchema(BaseSchema): def __init__(self, data): - self.email = Field('email', str, get(data, 'email')) - self.password = Field('password', str, get(data, 'password')) super().__init__() + self.add_schema(UserCredentialSchema, data) + def make_return_message(err, status_code=200): logging.debug('message: {}'.format(str(err))) @@ -128,7 +213,8 @@ def create_schema(specification, data): fields = {} for feature_name, feature_detail in specification['features'].items(): if not feature_detail['constant']: - fields[feature_name] = Field(feature_name, eval(feature_detail['unit']['type']), get(data, feature_name)) + fields[feature_name] = Field( + feature_name, eval(feature_detail['unit']['type']), **BaseSchema.get(data, feature_name) + ) return type('{}Schema'.format(specification['name']), (BaseSchema,), fields) - diff --git a/stripe_utils.py b/stripe_utils.py index 5ffb443..a9803af 100644 --- a/stripe_utils.py +++ b/stripe_utils.py @@ -245,13 +245,14 @@ class StripeUtils(object): return customer @handle_stripe_error - def create_customer(self, token, email, name=None): + def create_customer(self, token, email, name=None, address=None): if name is None or name.strip() == "": name = email customer = self.stripe.Customer.create( source=token, description=name, - email=email + email=email, + address=address ) return customer diff --git a/ucloud_pay.py b/ucloud_pay.py index edee113..e4d105f 100644 --- a/ucloud_pay.py +++ b/ucloud_pay.py @@ -1,5 +1,4 @@ import json -import time import logging from datetime import datetime @@ -10,60 +9,27 @@ from flask_restful import Resource, Api from config import etcd_client as client, config as config from stripe_utils import StripeUtils -from ldap_manager import LdapManager from schemas import ( make_return_message, ValidationException, UserRegisterPaymentSchema, AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema ) -from helper import ( - get_plan_id_from_product, get_user_friendly_product, get_order_id, -) - -logger = logging.getLogger() -logger.setLevel(logging.DEBUG) -log_formater = logging.Formatter('[%(filename)s:%(lineno)d] %(message)s') - -stream_logger = logging.StreamHandler() -stream_logger.setLevel(logging.DEBUG) -stream_logger.setFormatter(log_formater) - -logger.addHandler(stream_logger) - -app = Flask(__name__) -api = Api(app) -INIT_ORDER_ID = 0 - -ldap_manager = LdapManager(server=config['ldap']['server'], admin_dn=config['ldap']['admin_dn'], - admin_password=config['ldap']['admin_password']) - - -def calculate_charges(specification, data): - one_time_charge = 0 - recurring_charge = 0 - for feature_name, feature_detail in specification['features'].items(): - if feature_detail['constant']: - data[feature_name] = 1 - - if feature_detail['unit']['type'] != 'str': - one_time_charge += feature_detail['one_time_fee'] - recurring_charge += ( - feature_detail['price_per_unit_per_period'] * data[feature_name] / - feature_detail['unit']['value'] - ) - return one_time_charge, recurring_charge +from helper import get_plan_id_from_product, calculate_charges class ListProducts(Resource): @staticmethod def get(): products = client.get_prefix('/v1/products/', value_in_json=False) + products = [ + product + for product in [json.loads(p.value) for p in products] + if product['active'] + ] prod_dict = {} for p in products: - p = json.loads(p.value) prod_dict[p['usable-id']] = { 'name': p['name'], 'description': p['description'], - 'active': p['active'] } logger.debug('Products = {}'.format(prod_dict)) return prod_dict, 200 @@ -72,174 +38,170 @@ class ListProducts(Resource): class AddProduct(Resource): @staticmethod def post(): - data = request.json - logger.debug('Got data: {}'.format(str(data))) + data = request.get_json(silent=True) or {} try: + logger.debug('Got data: {}'.format(str(data))) validator = AddProductSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: - if ldap_manager.is_password_valid(data['email'], data['password']): - try: - user = ldap_manager.get('(mail={})'.format(data['email']))[0] - user = json.loads(user.entry_to_json()) - uid, ou, *dc = user['dn'].replace('ou=', '').replace('dc=', '').replace('uid=', '').split(',') - except Exception as err: - logger.error(str(err)) - return {'message': 'No such user exists'} + cleaned_values = validator.get_cleaned_values() + previous_product = cleaned_values.get('product', None) + if previous_product: + if not cleaned_values['update']: + return make_return_message('Product already exists. Pass --update to update the product.') else: - if ou != config['ldap']['internal_user_ou']: - logger.error('User (email=%s) does not have access to create product', validator.email) - return {'message': 'Forbidden'}, 403 - else: - product_uuid = uuid4().hex - product_key = '/v1/products/{}'.format(product_uuid) - product_value = validator.specs - product_value['uuid'] = product_uuid - - logger.debug('Adding product data: {}'.format(str(product_value))) - client.put(product_key, product_value, value_in_json=True) - return {'message': 'Product created'}, 200 - + product_uuid = previous_product.pop('uuid') else: - return {'message': 'Wrong Credentials'}, 403 + product_uuid = uuid4().hex + + product_value = cleaned_values['specs'] + + product_key = '/v1/products/{}'.format(product_uuid) + product_value['uuid'] = product_uuid + + logger.debug('Adding product data: {}'.format(str(product_value))) + client.put(product_key, product_value, value_in_json=True) + if not previous_product: + return make_return_message('Product created.') + else: + return make_return_message('Product updated.') class UserRegisterPayment(Resource): @staticmethod def post(): - data = request.json - logger.debug('Got data: {}'.format(str(data))) + data = request.get_json(silent=True) or {} + try: + logger.debug('Got data: {}'.format(str(data))) validator = UserRegisterPaymentSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: + cleaned_values = validator.get_cleaned_values() last4 = data['card_number'].strip()[-4:] - if ldap_manager.is_password_valid(validator.email, validator.password): - stripe_utils = StripeUtils() + stripe_utils = StripeUtils() - # Does customer already exist ? - stripe_customer = stripe_utils.get_stripe_customer_from_email(validator.email) + # Does customer already exist ? + stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail']) - # Does customer already exist ? - if stripe_customer is not None: - logger.debug('Customer {} exists already'.format(validator.email)) + # Does customer already exist ? + if stripe_customer is not None: + logger.debug('Customer {}-{} exists already'.format( + cleaned_values['username'], cleaned_values['user']['mail']) + ) - # Check if the card already exists - ce_response = stripe_utils.card_exists( - stripe_customer.id, cc_number=data['card_number'], - exp_month=int(data['expiry_month']), - exp_year=int(data['expiry_year']), - cvc=data['cvc']) + # Check if the card already exists + ce_response = stripe_utils.card_exists( + stripe_customer.id, cc_number=data['card_number'], + exp_month=int(data['expiry_month']), + exp_year=int(data['expiry_year']), + cvc=data['cvc']) - if ce_response['response_object']: - message = 'The given card ending in {} exists already.'.format(last4) - return make_return_message(message, 400) + if ce_response['response_object']: + message = 'The given card ending in {} exists already.'.format(last4) + return make_return_message(message, 400) - elif ce_response['response_object'] is False: - # Associate card with user - logger.debug('Adding card ending in {}'.format(last4)) - token_response = stripe_utils.get_token_from_card( - data['card_number'], data['cvc'], data['expiry_month'], - data['expiry_year'] - ) - if token_response['response_object']: - logger.debug('Token {}'.format(token_response['response_object'].id)) - resp = stripe_utils.associate_customer_card( - stripe_customer.id, token_response['response_object'].id - ) - if resp['response_object']: - return make_return_message( - 'Card ending in {} registered as your payment source'.format(last4) - ) - else: - return make_return_message('Error with payment gateway. Contact support', 400) - else: - return make_return_message('Error: {}'.format(ce_response['error']), 400) - else: - # Stripe customer does not exist, create a new one - logger.debug('Customer {} does not exist, creating new'.format(validator.email)) + elif ce_response['response_object'] is False: + # Associate card with user + logger.debug('Adding card ending in {}'.format(last4)) token_response = stripe_utils.get_token_from_card( - validator.card_number, validator.cvc, validator.expiry_month, - validator.expiry_year + data['card_number'], data['cvc'], data['expiry_month'], + data['expiry_year'] ) if token_response['response_object']: logger.debug('Token {}'.format(token_response['response_object'].id)) - - # Create stripe customer - stripe_customer_resp = stripe_utils.create_customer( - name=validator.card_holder_name, - token=token_response['response_object'].id, - email=validator.email + resp = stripe_utils.associate_customer_card( + stripe_customer.id, token_response['response_object'].id ) - stripe_customer = stripe_customer_resp['response_object'] - - if stripe_customer: - logger.debug('Created stripe customer {}'.format(stripe_customer.id)) + if resp['response_object']: return make_return_message( 'Card ending in {} registered as your payment source'.format(last4) ) - else: - return make_return_message('Error with card. Contact support', 400) else: return make_return_message('Error with payment gateway. Contact support', 400) + else: + return make_return_message('Error: {}'.format(ce_response['error']), 400) else: - return make_return_message('Wrong Credentials', 403) + # Stripe customer does not exist, create a new one + logger.debug( + 'Customer {} does not exist, creating new'.format(cleaned_values['user']['mail']) + ) + token_response = stripe_utils.get_token_from_card( + cleaned_values['card_number'], cleaned_values['cvc'], + cleaned_values['expiry_month'], cleaned_values['expiry_year'] + ) + if token_response['response_object']: + logger.debug('Token {}'.format(token_response['response_object'].id)) + + # Create stripe customer + stripe_customer_resp = stripe_utils.create_customer( + name=cleaned_values['card_holder_name'], + token=token_response['response_object'].id, + email=cleaned_values['user']['mail'], + address=cleaned_values['address'] + ) + stripe_customer = stripe_customer_resp['response_object'] + + if stripe_customer: + logger.debug('Created stripe customer {}'.format(stripe_customer.id)) + return make_return_message( + 'Card ending in {} registered as your payment source'.format(last4) + ) + else: + return make_return_message('Error with card. Contact support', 400) + else: + return make_return_message('Error with payment gateway. Contact support', 400) class ProductOrder(Resource): @staticmethod def post(): - data = request.json + data = request.get_json(silent=True) or {} + try: validator = ProductOrderSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: - if ldap_manager.is_password_valid(validator.email, validator.password): - stripe_utils = StripeUtils() - logger.debug('Product ID = {}'.format(validator.product_id)) + cleaned_values = validator.get_cleaned_values() + stripe_utils = StripeUtils() - # Validate the given product is ok - product = client.get('/v1/products/{}'.format(validator.product_id), value_in_json=True) - if not product: - return make_return_message('Invalid Product', 400) + product = cleaned_values['product'] - product = product.value + # Check the user has a payment source added + stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail']) - customer_previous_orders = client.get_prefix( - '/v1/user/{}'.format(validator.email), value_in_json=True - ) - membership = next(filter(lambda o: o.value['product'] == 'membership', customer_previous_orders), None) - if membership is None and data['product_id'] != 'membership': - return make_return_message('Please buy membership first to use this facility') + if not stripe_customer or len(stripe_customer.sources) == 0: + return make_return_message('Please register your payment method first.', 400) - logger.debug('Got product {}'.format(product)) + try: + product_schema = create_schema(product, data) + product_schema = product_schema() + product_schema.is_valid() + except ValidationException as err: + return make_return_message(err, 400) + else: + transformed_data = product_schema.get_cleaned_values() + logger.debug('Tranformed data: {}'.format(transformed_data)) + one_time_charge, recurring_charge = calculate_charges(product, transformed_data) + recurring_charge = int(recurring_charge) - # Check the user has a payment source added - stripe_customer = stripe_utils.get_stripe_customer_from_email(validator.email) - - if not stripe_customer or len(stripe_customer.sources) == 0: - return make_return_message('Please register first.', 400) - - try: - product_schema = create_schema(product, data) - product_schema = product_schema() - product_schema.is_valid() - except ValidationException as err: - return make_return_message(err, 400) - else: - transformed_data = product_schema.return_data() - logger.debug('Tranformed data: {}'.format(transformed_data)) - one_time_charge, recurring_charge = calculate_charges(product, transformed_data) - recurring_charge = int(recurring_charge) + if not cleaned_values['pay']: + return make_return_message( + 'You would be charged {} CHF one time and {} CHF every {}. ' + 'Add --pay to command to order.'.format( + one_time_charge, recurring_charge, product['recurring_period'] + ) + ) + with client.client.lock('product-order') as lock: # Initiate a one-time/subscription based on product type if recurring_charge > 0: logger.debug('Product {} is recurring payment'.format(product['name'])) @@ -262,13 +224,26 @@ class ProductOrder(Resource): ) else: order_obj = { - 'order_id': get_order_id(), - 'ordered_at': int(time.time()), + 'order-id': uuid4().hex, + 'ordered-at': datetime.now().isoformat(), 'product': product['usable-id'], + 'one-time-price': one_time_charge, + 'recurring-price': recurring_charge, + 'recurring-period': product['recurring_period'] + } + client.put( + '/v1/user/{}/orders/{}'.format( + cleaned_values['username'], order_obj['order-id'] + ), + order_obj, value_in_json=True + ) + product['quantity'] -= 1 + client.put('/v1/products/{}'.format(product['uuid']), product, value_in_json=True) + + return { + 'message': 'Order Successful.', + **order_obj } - client.put('/v1/user/{}/orders'.format(validator.email), order_obj, value_in_json=True) - order_obj['ordered_at'] = datetime.fromtimestamp(order_obj['ordered_at']).strftime('%c') - return make_return_message('Order Successful. Order Details: {}'.format(order_obj)) else: logger.error('Could not create plan {}'.format(plan_id)) @@ -283,63 +258,71 @@ class ProductOrder(Resource): # Check if the payment was approved if not stripe_onetime_charge: msg = charge_response.get('error') - return make_return_message( - 'Error subscribing to plan. Details: {}'.format(msg), 400 - ) + return make_return_message('Error subscribing to plan. Details: {}'.format(msg), 400) order_obj = { - 'order_id': get_order_id(), - 'ordered_at': int(time.time()), + 'order-id': uuid4().hex, + 'ordered-at': datetime.now().isoformat(), 'product': product['usable-id'], + 'one-time-price': one_time_charge, } client.put( - '/v1/user/{}/orders'.format(validator.email),order_obj, - value_in_json=True + '/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']), + order_obj, value_in_json=True ) - order_obj['ordered_at'] = datetime.fromtimestamp(order_obj['ordered_at']).strftime('%c') - return {'message': 'Order successful', 'order_details': order_obj}, 200 - else: - return make_return_message('Wrong Credentials', 400) + product['quantity'] -= 1 + client.put('/v1/products/{}'.format(product['uuid']), product, value_in_json=True) + + return {'message': 'Order successful', **order_obj}, 200 class OrderList(Resource): @staticmethod - def get(): - data = request.json + def post(): + data = request.get_json(silent=True) or {} + try: validator = OrderListSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: - print(validator.email, validator.password) - if not ldap_manager.is_password_valid(validator.email, validator.password): - return {'message': 'Wrong Credentials'}, 403 - - orders = client.get_prefix('/v1/user/{}/orders'.format(validator.email), value_in_json=True) + cleaned_values = validator.get_cleaned_values() + orders = client.get_prefix( + '/v1/user/{}/orders'.format(cleaned_values['username']), value_in_json=True + ) orders_dict = { - order.value['order_id']: { - 'ordered-at': datetime.fromtimestamp(order.value['ordered_at']).strftime('%c'), - 'product': order.value['product'] + order.value['order-id']: { + **order.value } for order in orders } - # for p in orders: - # order_dict = p.value - # order_dict['ordered_at'] = datetime.fromtimestamp( - # order_dict['ordered_at']).strftime('%c') - # order_dict['product'] = order_dict['product']['name'] - # orders_dict[order_dict['order_id']] = order_dict logger.debug('Orders = {}'.format(orders_dict)) - return orders_dict, 200 - - -api.add_resource(ListProducts, '/product/list') -api.add_resource(AddProduct, '/product/add') -api.add_resource(ProductOrder, '/product/order') -api.add_resource(UserRegisterPayment, '/user/register_payment') -api.add_resource(OrderList, '/order/list') + return {'orders': orders_dict}, 200 if __name__ == '__main__': - app.run(host='::', port=config['app']['port'], debug=True) \ No newline at end of file + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + log_formater = logging.Formatter('[%(filename)s:%(lineno)d] %(message)s') + + stream_logger = logging.StreamHandler() + stream_logger.setFormatter(log_formater) + + # file_logger = logging.FileHandler('log.txt') + # file_logger.setLevel(logging.DEBUG) + # file_logger.setFormatter(log_formater) + + logger.addHandler(stream_logger) + # logger.addHandler(file_logger) + + app = Flask(__name__) + + api = Api(app) + api.add_resource(ListProducts, '/product/list') + api.add_resource(AddProduct, '/product/add') + api.add_resource(ProductOrder, '/product/order') + api.add_resource(UserRegisterPayment, '/user/register_payment') + api.add_resource(OrderList, '/order/list') + + app.run(host='::', port=config['app']['port'], debug=True)