forked from uncloud/uncloud
Many more changes
This commit is contained in:
parent
da54a59ca2
commit
1a76d2b5f3
11 changed files with 386 additions and 283 deletions
|
@ -1,8 +1,11 @@
|
||||||
import configparser
|
import configparser
|
||||||
from etcd_wrapper import EtcdWrapper
|
from etcd_wrapper import EtcdWrapper
|
||||||
|
from ldap_manager import LdapManager
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read('pay.conf')
|
config.read('pay.conf')
|
||||||
|
|
||||||
etcd_client = EtcdWrapper(host=config['etcd']['host'], port=config['etcd']['port'])
|
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'])
|
38
helper.py
38
helper.py
|
@ -1,26 +1,14 @@
|
||||||
import config
|
import logging
|
||||||
from stripe_utils import StripeUtils
|
|
||||||
|
|
||||||
etcd_client = config.etcd_client
|
from stripe_utils import StripeUtils
|
||||||
|
|
||||||
|
|
||||||
def get_plan_id_from_product(product):
|
def get_plan_id_from_product(product):
|
||||||
plan_id = 'ucloud-v1-'
|
plan_id = 'ucloud-v1-'
|
||||||
plan_id += product['name'].strip().replace(' ', '-')
|
plan_id += product['name'].strip().replace(' ', '-')
|
||||||
# plan_id += '-' + product['type']
|
|
||||||
return plan_id
|
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):
|
def get_pricing(price_in_chf_cents, product_type, recurring_period):
|
||||||
if product_type == 'recurring':
|
if product_type == 'recurring':
|
||||||
return 'CHF {}/{}'.format(price_in_chf_cents/100, recurring_period)
|
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
|
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)
|
products = etcd_client.get_prefix('/v1/products/', value_in_json=True)
|
||||||
for p in products:
|
for p in products:
|
||||||
if p.value['usable-id'] == usable_id:
|
if p.value['usable-id'] == usable_id:
|
||||||
print(p.value['uuid'], usable_id)
|
return p.value
|
||||||
return p.value['uuid']
|
|
||||||
return None
|
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
|
||||||
|
|
|
@ -22,12 +22,17 @@ class LdapManager:
|
||||||
r = Reader(**kwargs)
|
r = Reader(**kwargs)
|
||||||
return r.search()
|
return r.search()
|
||||||
|
|
||||||
def is_password_valid(self, email, password, **kwargs):
|
def is_password_valid(self, query_value, password, query_key='mail', **kwargs):
|
||||||
entries = self.get(query='(mail={})'.format(email), **kwargs)
|
entries = self.get(query='({}={})'.format(query_key, query_value), **kwargs)
|
||||||
if entries:
|
if entries:
|
||||||
password_in_ldap = entries[0].userPassword.value
|
password_in_ldap = entries[0].userPassword.value
|
||||||
return self._check_password(password_in_ldap, password)
|
found = self._check_password(password_in_ldap, password)
|
||||||
return False
|
if not found:
|
||||||
|
raise Exception('Invalid Password')
|
||||||
|
else:
|
||||||
|
return entries[0]
|
||||||
|
else:
|
||||||
|
raise ValueError('Such {}={} not found'.format(query_key, query_value))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_password(tagged_digest_salt, password):
|
def _check_password(tagged_digest_salt, password):
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"name": "IPv6 Only Django Hosting",
|
"name": "IPv6 Only Django Hosting",
|
||||||
"description": "Host your Django application on our shiny IPv6 Only VM",
|
"description": "Host your Django application on our shiny IPv6 Only VM",
|
||||||
"recurring_period": "month",
|
"recurring_period": "month",
|
||||||
|
"quantity": "inf",
|
||||||
"features": {
|
"features": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"unit": {"value": 1, "type":"int"},
|
"unit": {"value": 1, "type":"int"},
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"name": "IPv6 Only VM",
|
"name": "IPv6 Only VM",
|
||||||
"description": "IPv6 Only VM are accessible to only those having IPv6 for themselves",
|
"description": "IPv6 Only VM are accessible to only those having IPv6 for themselves",
|
||||||
"recurring_period": "month",
|
"recurring_period": "month",
|
||||||
|
"quantity": "inf",
|
||||||
"features": {
|
"features": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"unit": {"value": 1, "type":"int"},
|
"unit": {"value": 1, "type":"int"},
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"name": "IPv6 Only VPN",
|
"name": "IPv6 Only VPN",
|
||||||
"description": "IPv6 VPN enable you to access IPv6 only websites and more",
|
"description": "IPv6 VPN enable you to access IPv6 only websites and more",
|
||||||
"recurring_period": "month",
|
"recurring_period": "month",
|
||||||
|
"quantity": "inf",
|
||||||
"features": {
|
"features": {
|
||||||
"vpn": {
|
"vpn": {
|
||||||
"unit": {"value": 1, "type": "int"},
|
"unit": {"value": 1, "type": "int"},
|
||||||
|
|
16
products/ipv6box.json
Normal file
16
products/ipv6box.json
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,15 @@
|
||||||
"active": true,
|
"active": true,
|
||||||
"name": "Membership",
|
"name": "Membership",
|
||||||
"description": "Membership to use uncloud-pay",
|
"description": "Membership to use uncloud-pay",
|
||||||
"recurring_period": "eternity",
|
"recurring_period": "month",
|
||||||
|
"quantity": "inf",
|
||||||
"features": {
|
"features": {
|
||||||
"membership": {
|
"membership": {
|
||||||
"unit": {"value": 1, "type":"int"},
|
"unit": {"value": 1, "type":"int"},
|
||||||
"price_per_unit_per_period": 0,
|
"price_per_unit_per_period": 5,
|
||||||
"one_time_fee": 5,
|
"one_time_fee": 0,
|
||||||
"constant": true
|
"constant": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"max_per_user": "1"
|
||||||
}
|
}
|
||||||
|
|
176
schemas.py
176
schemas.py
|
@ -1,7 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
import config
|
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
|
etcd_client = config.etcd_client
|
||||||
|
|
||||||
|
@ -11,18 +14,15 @@ class ValidationException(Exception):
|
||||||
|
|
||||||
|
|
||||||
class Field:
|
class Field:
|
||||||
def __init__(self, _name, _type, _value=None, validators=None):
|
def __init__(self, _name, _type, _value=None, validators=None, disable_validation=False):
|
||||||
if validators is None:
|
self.validation_disabled = disable_validation
|
||||||
validators = []
|
|
||||||
|
|
||||||
assert isinstance(validators, list)
|
|
||||||
|
|
||||||
self.name = _name
|
self.name = _name
|
||||||
self.value = _value
|
self.value = _value
|
||||||
self.type = _type
|
self.type = _type
|
||||||
self.validators = validators
|
self.validators = validators or []
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
if not self.validation_disabled:
|
||||||
if not isinstance(self.value, self.type):
|
if not isinstance(self.value, self.type):
|
||||||
try:
|
try:
|
||||||
self.value = self.type(self.value)
|
self.value = self.type(self.value)
|
||||||
|
@ -38,31 +38,49 @@ class Field:
|
||||||
|
|
||||||
class BaseSchema:
|
class BaseSchema:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.fields = [getattr(self, field) for field in dir(self) if isinstance(getattr(self, field), Field)]
|
self.objects = {}
|
||||||
|
|
||||||
def validation(self):
|
def validation(self):
|
||||||
# custom validation is optional
|
# custom validation is optional
|
||||||
return True
|
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):
|
def is_valid(self):
|
||||||
for field in self.fields:
|
for field in self.get_fields():
|
||||||
field.is_valid()
|
field.is_valid()
|
||||||
|
|
||||||
for parent in self.__class__.__bases__:
|
|
||||||
parent.validation(self)
|
|
||||||
|
|
||||||
self.validation()
|
self.validation()
|
||||||
|
|
||||||
for field in self.fields:
|
def get_cleaned_values(self):
|
||||||
setattr(self, field.name, field.value)
|
field_kv_dict = {
|
||||||
|
|
||||||
def return_data(self):
|
|
||||||
return {
|
|
||||||
field.name: field.value
|
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 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):
|
def get(dictionary: dict, key: str, return_default=False, default=None):
|
||||||
if dictionary is None:
|
if dictionary is None:
|
||||||
raise ValidationException('No data provided at all.')
|
raise ValidationException('No data provided at all.')
|
||||||
|
@ -70,54 +88,121 @@ def get(dictionary: dict, key: str, return_default=False, default=None):
|
||||||
value = dictionary[key]
|
value = dictionary[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if return_default:
|
if return_default:
|
||||||
return default
|
return {'_value': default, 'disable_validation': True}
|
||||||
raise ValidationException("Missing data for '{}' field.".format(key))
|
raise ValidationException("Missing data for '{}' field.".format(key))
|
||||||
else:
|
else:
|
||||||
return value
|
return {'_value': value, 'disable_validation': False}
|
||||||
|
|
||||||
|
|
||||||
class AddProductSchema(BaseSchema):
|
class AddProductSchema(BaseSchema):
|
||||||
def __init__(self, data):
|
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__()
|
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):
|
class UserRegisterPaymentSchema(BaseSchema):
|
||||||
def __init__(self, data):
|
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__()
|
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):
|
class ProductOrderSchema(BaseSchema):
|
||||||
def __init__(self, data):
|
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__()
|
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):
|
def product_id_validation(self):
|
||||||
product_uuid = resolve_product_usable_id(self.product_id.value, etcd_client)
|
product = resolve_product(self.product_id.value, etcd_client)
|
||||||
if product_uuid:
|
if product:
|
||||||
self.product_id.value = product_uuid
|
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:
|
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):
|
class OrderListSchema(BaseSchema):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.email = Field('email', str, get(data, 'email'))
|
|
||||||
self.password = Field('password', str, get(data, 'password'))
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.add_schema(UserCredentialSchema, data)
|
||||||
|
|
||||||
|
|
||||||
def make_return_message(err, status_code=200):
|
def make_return_message(err, status_code=200):
|
||||||
logging.debug('message: {}'.format(str(err)))
|
logging.debug('message: {}'.format(str(err)))
|
||||||
|
@ -128,7 +213,8 @@ def create_schema(specification, data):
|
||||||
fields = {}
|
fields = {}
|
||||||
for feature_name, feature_detail in specification['features'].items():
|
for feature_name, feature_detail in specification['features'].items():
|
||||||
if not feature_detail['constant']:
|
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)
|
return type('{}Schema'.format(specification['name']), (BaseSchema,), fields)
|
||||||
|
|
||||||
|
|
|
@ -245,13 +245,14 @@ class StripeUtils(object):
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
@handle_stripe_error
|
@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() == "":
|
if name is None or name.strip() == "":
|
||||||
name = email
|
name = email
|
||||||
customer = self.stripe.Customer.create(
|
customer = self.stripe.Customer.create(
|
||||||
source=token,
|
source=token,
|
||||||
description=name,
|
description=name,
|
||||||
email=email
|
email=email,
|
||||||
|
address=address
|
||||||
)
|
)
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
|
|
233
ucloud_pay.py
233
ucloud_pay.py
|
@ -1,5 +1,4 @@
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from datetime import datetime
|
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 config import etcd_client as client, config as config
|
||||||
from stripe_utils import StripeUtils
|
from stripe_utils import StripeUtils
|
||||||
from ldap_manager import LdapManager
|
|
||||||
from schemas import (
|
from schemas import (
|
||||||
make_return_message, ValidationException, UserRegisterPaymentSchema,
|
make_return_message, ValidationException, UserRegisterPaymentSchema,
|
||||||
AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema
|
AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema
|
||||||
)
|
)
|
||||||
from helper import (
|
from helper import get_plan_id_from_product, calculate_charges
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class ListProducts(Resource):
|
class ListProducts(Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get():
|
def get():
|
||||||
products = client.get_prefix('/v1/products/', value_in_json=False)
|
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 = {}
|
prod_dict = {}
|
||||||
for p in products:
|
for p in products:
|
||||||
p = json.loads(p.value)
|
|
||||||
prod_dict[p['usable-id']] = {
|
prod_dict[p['usable-id']] = {
|
||||||
'name': p['name'],
|
'name': p['name'],
|
||||||
'description': p['description'],
|
'description': p['description'],
|
||||||
'active': p['active']
|
|
||||||
}
|
}
|
||||||
logger.debug('Products = {}'.format(prod_dict))
|
logger.debug('Products = {}'.format(prod_dict))
|
||||||
return prod_dict, 200
|
return prod_dict, 200
|
||||||
|
@ -72,63 +38,63 @@ class ListProducts(Resource):
|
||||||
class AddProduct(Resource):
|
class AddProduct(Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def post():
|
def post():
|
||||||
data = request.json
|
data = request.get_json(silent=True) or {}
|
||||||
logger.debug('Got data: {}'.format(str(data)))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Got data: {}'.format(str(data)))
|
||||||
validator = AddProductSchema(data)
|
validator = AddProductSchema(data)
|
||||||
validator.is_valid()
|
validator.is_valid()
|
||||||
except ValidationException as err:
|
except ValidationException as err:
|
||||||
return make_return_message(err, 400)
|
return make_return_message(err, 400)
|
||||||
else:
|
else:
|
||||||
if ldap_manager.is_password_valid(data['email'], data['password']):
|
cleaned_values = validator.get_cleaned_values()
|
||||||
try:
|
previous_product = cleaned_values.get('product', None)
|
||||||
user = ldap_manager.get('(mail={})'.format(data['email']))[0]
|
if previous_product:
|
||||||
user = json.loads(user.entry_to_json())
|
if not cleaned_values['update']:
|
||||||
uid, ou, *dc = user['dn'].replace('ou=', '').replace('dc=', '').replace('uid=', '').split(',')
|
return make_return_message('Product already exists. Pass --update to update the product.')
|
||||||
except Exception as err:
|
|
||||||
logger.error(str(err))
|
|
||||||
return {'message': 'No such user exists'}
|
|
||||||
else:
|
else:
|
||||||
if ou != config['ldap']['internal_user_ou']:
|
product_uuid = previous_product.pop('uuid')
|
||||||
logger.error('User (email=%s) does not have access to create product', validator.email)
|
|
||||||
return {'message': 'Forbidden'}, 403
|
|
||||||
else:
|
else:
|
||||||
product_uuid = uuid4().hex
|
product_uuid = uuid4().hex
|
||||||
|
|
||||||
|
product_value = cleaned_values['specs']
|
||||||
|
|
||||||
product_key = '/v1/products/{}'.format(product_uuid)
|
product_key = '/v1/products/{}'.format(product_uuid)
|
||||||
product_value = validator.specs
|
|
||||||
product_value['uuid'] = product_uuid
|
product_value['uuid'] = product_uuid
|
||||||
|
|
||||||
logger.debug('Adding product data: {}'.format(str(product_value)))
|
logger.debug('Adding product data: {}'.format(str(product_value)))
|
||||||
client.put(product_key, product_value, value_in_json=True)
|
client.put(product_key, product_value, value_in_json=True)
|
||||||
return {'message': 'Product created'}, 200
|
if not previous_product:
|
||||||
|
return make_return_message('Product created.')
|
||||||
else:
|
else:
|
||||||
return {'message': 'Wrong Credentials'}, 403
|
return make_return_message('Product updated.')
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterPayment(Resource):
|
class UserRegisterPayment(Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def post():
|
def post():
|
||||||
data = request.json
|
data = request.get_json(silent=True) or {}
|
||||||
logger.debug('Got data: {}'.format(str(data)))
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Got data: {}'.format(str(data)))
|
||||||
validator = UserRegisterPaymentSchema(data)
|
validator = UserRegisterPaymentSchema(data)
|
||||||
validator.is_valid()
|
validator.is_valid()
|
||||||
except ValidationException as err:
|
except ValidationException as err:
|
||||||
return make_return_message(err, 400)
|
return make_return_message(err, 400)
|
||||||
else:
|
else:
|
||||||
|
cleaned_values = validator.get_cleaned_values()
|
||||||
last4 = data['card_number'].strip()[-4:]
|
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 ?
|
# Does customer already exist ?
|
||||||
stripe_customer = stripe_utils.get_stripe_customer_from_email(validator.email)
|
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
|
||||||
|
|
||||||
# Does customer already exist ?
|
# Does customer already exist ?
|
||||||
if stripe_customer is not None:
|
if stripe_customer is not None:
|
||||||
logger.debug('Customer {} exists already'.format(validator.email))
|
logger.debug('Customer {}-{} exists already'.format(
|
||||||
|
cleaned_values['username'], cleaned_values['user']['mail'])
|
||||||
|
)
|
||||||
|
|
||||||
# Check if the card already exists
|
# Check if the card already exists
|
||||||
ce_response = stripe_utils.card_exists(
|
ce_response = stripe_utils.card_exists(
|
||||||
|
@ -163,19 +129,22 @@ class UserRegisterPayment(Resource):
|
||||||
return make_return_message('Error: {}'.format(ce_response['error']), 400)
|
return make_return_message('Error: {}'.format(ce_response['error']), 400)
|
||||||
else:
|
else:
|
||||||
# Stripe customer does not exist, create a new one
|
# Stripe customer does not exist, create a new one
|
||||||
logger.debug('Customer {} does not exist, creating new'.format(validator.email))
|
logger.debug(
|
||||||
|
'Customer {} does not exist, creating new'.format(cleaned_values['user']['mail'])
|
||||||
|
)
|
||||||
token_response = stripe_utils.get_token_from_card(
|
token_response = stripe_utils.get_token_from_card(
|
||||||
validator.card_number, validator.cvc, validator.expiry_month,
|
cleaned_values['card_number'], cleaned_values['cvc'],
|
||||||
validator.expiry_year
|
cleaned_values['expiry_month'], cleaned_values['expiry_year']
|
||||||
)
|
)
|
||||||
if token_response['response_object']:
|
if token_response['response_object']:
|
||||||
logger.debug('Token {}'.format(token_response['response_object'].id))
|
logger.debug('Token {}'.format(token_response['response_object'].id))
|
||||||
|
|
||||||
# Create stripe customer
|
# Create stripe customer
|
||||||
stripe_customer_resp = stripe_utils.create_customer(
|
stripe_customer_resp = stripe_utils.create_customer(
|
||||||
name=validator.card_holder_name,
|
name=cleaned_values['card_holder_name'],
|
||||||
token=token_response['response_object'].id,
|
token=token_response['response_object'].id,
|
||||||
email=validator.email
|
email=cleaned_values['user']['mail'],
|
||||||
|
address=cleaned_values['address']
|
||||||
)
|
)
|
||||||
stripe_customer = stripe_customer_resp['response_object']
|
stripe_customer = stripe_customer_resp['response_object']
|
||||||
|
|
||||||
|
@ -188,45 +157,29 @@ class UserRegisterPayment(Resource):
|
||||||
return make_return_message('Error with card. Contact support', 400)
|
return make_return_message('Error with card. Contact support', 400)
|
||||||
else:
|
else:
|
||||||
return make_return_message('Error with payment gateway. Contact support', 400)
|
return make_return_message('Error with payment gateway. Contact support', 400)
|
||||||
else:
|
|
||||||
return make_return_message('Wrong Credentials', 403)
|
|
||||||
|
|
||||||
|
|
||||||
class ProductOrder(Resource):
|
class ProductOrder(Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def post():
|
def post():
|
||||||
data = request.json
|
data = request.get_json(silent=True) or {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validator = ProductOrderSchema(data)
|
validator = ProductOrderSchema(data)
|
||||||
validator.is_valid()
|
validator.is_valid()
|
||||||
except ValidationException as err:
|
except ValidationException as err:
|
||||||
return make_return_message(err, 400)
|
return make_return_message(err, 400)
|
||||||
else:
|
else:
|
||||||
if ldap_manager.is_password_valid(validator.email, validator.password):
|
cleaned_values = validator.get_cleaned_values()
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
logger.debug('Product ID = {}'.format(validator.product_id))
|
|
||||||
|
|
||||||
# Validate the given product is ok
|
product = cleaned_values['product']
|
||||||
product = client.get('/v1/products/{}'.format(validator.product_id), value_in_json=True)
|
|
||||||
if not product:
|
|
||||||
return make_return_message('Invalid Product', 400)
|
|
||||||
|
|
||||||
product = product.value
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
logger.debug('Got product {}'.format(product))
|
|
||||||
|
|
||||||
# Check the user has a payment source added
|
# Check the user has a payment source added
|
||||||
stripe_customer = stripe_utils.get_stripe_customer_from_email(validator.email)
|
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
|
||||||
|
|
||||||
if not stripe_customer or len(stripe_customer.sources) == 0:
|
if not stripe_customer or len(stripe_customer.sources) == 0:
|
||||||
return make_return_message('Please register first.', 400)
|
return make_return_message('Please register your payment method first.', 400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
product_schema = create_schema(product, data)
|
product_schema = create_schema(product, data)
|
||||||
|
@ -235,11 +188,20 @@ class ProductOrder(Resource):
|
||||||
except ValidationException as err:
|
except ValidationException as err:
|
||||||
return make_return_message(err, 400)
|
return make_return_message(err, 400)
|
||||||
else:
|
else:
|
||||||
transformed_data = product_schema.return_data()
|
transformed_data = product_schema.get_cleaned_values()
|
||||||
logger.debug('Tranformed data: {}'.format(transformed_data))
|
logger.debug('Tranformed data: {}'.format(transformed_data))
|
||||||
one_time_charge, recurring_charge = calculate_charges(product, transformed_data)
|
one_time_charge, recurring_charge = calculate_charges(product, transformed_data)
|
||||||
recurring_charge = int(recurring_charge)
|
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
|
# Initiate a one-time/subscription based on product type
|
||||||
if recurring_charge > 0:
|
if recurring_charge > 0:
|
||||||
logger.debug('Product {} is recurring payment'.format(product['name']))
|
logger.debug('Product {} is recurring payment'.format(product['name']))
|
||||||
|
@ -262,13 +224,26 @@ class ProductOrder(Resource):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
order_obj = {
|
order_obj = {
|
||||||
'order_id': get_order_id(),
|
'order-id': uuid4().hex,
|
||||||
'ordered_at': int(time.time()),
|
'ordered-at': datetime.now().isoformat(),
|
||||||
'product': product['usable-id'],
|
'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:
|
else:
|
||||||
logger.error('Could not create plan {}'.format(plan_id))
|
logger.error('Could not create plan {}'.format(plan_id))
|
||||||
|
|
||||||
|
@ -283,63 +258,71 @@ class ProductOrder(Resource):
|
||||||
# Check if the payment was approved
|
# Check if the payment was approved
|
||||||
if not stripe_onetime_charge:
|
if not stripe_onetime_charge:
|
||||||
msg = charge_response.get('error')
|
msg = charge_response.get('error')
|
||||||
return make_return_message(
|
return make_return_message('Error subscribing to plan. Details: {}'.format(msg), 400)
|
||||||
'Error subscribing to plan. Details: {}'.format(msg), 400
|
|
||||||
)
|
|
||||||
|
|
||||||
order_obj = {
|
order_obj = {
|
||||||
'order_id': get_order_id(),
|
'order-id': uuid4().hex,
|
||||||
'ordered_at': int(time.time()),
|
'ordered-at': datetime.now().isoformat(),
|
||||||
'product': product['usable-id'],
|
'product': product['usable-id'],
|
||||||
|
'one-time-price': one_time_charge,
|
||||||
}
|
}
|
||||||
client.put(
|
client.put(
|
||||||
'/v1/user/{}/orders'.format(validator.email),order_obj,
|
'/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']),
|
||||||
value_in_json=True
|
order_obj, value_in_json=True
|
||||||
)
|
)
|
||||||
order_obj['ordered_at'] = datetime.fromtimestamp(order_obj['ordered_at']).strftime('%c')
|
product['quantity'] -= 1
|
||||||
return {'message': 'Order successful', 'order_details': order_obj}, 200
|
client.put('/v1/products/{}'.format(product['uuid']), product, value_in_json=True)
|
||||||
else:
|
|
||||||
return make_return_message('Wrong Credentials', 400)
|
return {'message': 'Order successful', **order_obj}, 200
|
||||||
|
|
||||||
|
|
||||||
class OrderList(Resource):
|
class OrderList(Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get():
|
def post():
|
||||||
data = request.json
|
data = request.get_json(silent=True) or {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validator = OrderListSchema(data)
|
validator = OrderListSchema(data)
|
||||||
validator.is_valid()
|
validator.is_valid()
|
||||||
except ValidationException as err:
|
except ValidationException as err:
|
||||||
return make_return_message(err, 400)
|
return make_return_message(err, 400)
|
||||||
else:
|
else:
|
||||||
print(validator.email, validator.password)
|
cleaned_values = validator.get_cleaned_values()
|
||||||
if not ldap_manager.is_password_valid(validator.email, validator.password):
|
orders = client.get_prefix(
|
||||||
return {'message': 'Wrong Credentials'}, 403
|
'/v1/user/{}/orders'.format(cleaned_values['username']), value_in_json=True
|
||||||
|
)
|
||||||
orders = client.get_prefix('/v1/user/{}/orders'.format(validator.email), value_in_json=True)
|
|
||||||
orders_dict = {
|
orders_dict = {
|
||||||
order.value['order_id']: {
|
order.value['order-id']: {
|
||||||
'ordered-at': datetime.fromtimestamp(order.value['ordered_at']).strftime('%c'),
|
**order.value
|
||||||
'product': order.value['product']
|
|
||||||
}
|
}
|
||||||
for order in orders
|
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))
|
logger.debug('Orders = {}'.format(orders_dict))
|
||||||
return orders_dict, 200
|
return {'orders': orders_dict}, 200
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
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(ListProducts, '/product/list')
|
||||||
api.add_resource(AddProduct, '/product/add')
|
api.add_resource(AddProduct, '/product/add')
|
||||||
api.add_resource(ProductOrder, '/product/order')
|
api.add_resource(ProductOrder, '/product/order')
|
||||||
api.add_resource(UserRegisterPayment, '/user/register_payment')
|
api.add_resource(UserRegisterPayment, '/user/register_payment')
|
||||||
api.add_resource(OrderList, '/order/list')
|
api.add_resource(OrderList, '/order/list')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='::', port=config['app']['port'], debug=True)
|
app.run(host='::', port=config['app']['port'], debug=True)
|
Loading…
Reference in a new issue