import json import time import logging from datetime import datetime from uuid import uuid4 from flask import Flask, request 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 class ListProducts(Resource): @staticmethod def get(): products = client.get_prefix('/v1/products/', value_in_json=False) 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 class AddProduct(Resource): @staticmethod def post(): data = request.json logger.debug('Got data: {}'.format(str(data))) try: 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'} 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 else: return {'message': 'Wrong Credentials'}, 403 class UserRegisterPayment(Resource): @staticmethod def post(): data = request.json logger.debug('Got data: {}'.format(str(data))) try: validator = UserRegisterPaymentSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: last4 = data['card_number'].strip()[-4:] if ldap_manager.is_password_valid(validator.email, validator.password): stripe_utils = StripeUtils() # Does customer already exist ? stripe_customer = stripe_utils.get_stripe_customer_from_email(validator.email) # Does customer already exist ? if stripe_customer is not None: logger.debug('Customer {} exists already'.format(validator.email)) # 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) 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)) token_response = stripe_utils.get_token_from_card( validator.card_number, validator.cvc, validator.expiry_month, validator.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 ) 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) else: return make_return_message('Wrong Credentials', 403) class ProductOrder(Resource): @staticmethod def post(): data = request.json 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)) # 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 = 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 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) # Initiate a one-time/subscription based on product type if recurring_charge > 0: logger.debug('Product {} is recurring payment'.format(product['name'])) plan_id = get_plan_id_from_product(product) res = stripe_utils.get_or_create_stripe_plan( product_name=product['name'], stripe_plan_id=plan_id, amount=recurring_charge, interval=product['recurring_period'], ) if res['response_object']: logger.debug('Obtained plan {}'.format(plan_id)) subscription_res = stripe_utils.subscribe_customer_to_plan( stripe_customer.id, [{'plan': plan_id}] ) subscription_obj = subscription_res['response_object'] if subscription_obj is None or subscription_obj.status != 'active': return make_return_message( 'Error subscribing to plan. Detail: {}'.format(subscription_res['error']), 400 ) else: order_obj = { 'order_id': get_order_id(), 'ordered_at': int(time.time()), 'product': product['usable-id'], } 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)) elif recurring_charge == 0 and one_time_charge > 0: logger.debug('Product {} is one-time payment'.format(product['name'])) charge_response = stripe_utils.make_charge( amount=one_time_charge, customer=stripe_customer.id ) stripe_onetime_charge = charge_response.get('response_object') # 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 ) order_obj = { 'order_id': get_order_id(), 'ordered_at': int(time.time()), 'product': product['usable-id'], } 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 {'message': 'Order successful', 'order_details': order_obj}, 200 else: return make_return_message('Wrong Credentials', 400) class OrderList(Resource): @staticmethod def get(): data = request.json 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) orders_dict = { order.value['order_id']: { 'ordered-at': datetime.fromtimestamp(order.value['ordered_at']).strftime('%c'), 'product': order.value['product'] } 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') if __name__ == '__main__': app.run(host='::', port=config['app']['port'], debug=True)