import logging from datetime import datetime from uuid import uuid4 from flask import Flask, request from flask_restful import Resource, Api from werkzeug.exceptions import HTTPException from config import etcd_client as client, config as config from stripe_utils import StripeUtils from schemas import ( make_return_message, ValidationException, UserRegisterPaymentSchema, AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema ) from helper import get_plan_id_from_product, calculate_charges class ListProducts(Resource): @staticmethod def get(): products = client.get_prefix('/v1/products/') products = [ product for product in [p.value for p in products] if product['active'] ] prod_dict = {} for p in products: prod_dict[p['usable-id']] = { 'name': p['name'], 'description': p['description'], } logger.debug('Products = {}'.format(prod_dict)) return prod_dict, 200 class AddProduct(Resource): @staticmethod def post(): 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: 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: product_uuid = previous_product.pop('uuid') else: 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) 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.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:] stripe_utils = StripeUtils() # 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( 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']) 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(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.get_json(silent=True) or {} try: validator = ProductOrderSchema(data) validator.is_valid() except ValidationException as err: return make_return_message(err, 400) else: cleaned_values = validator.get_cleaned_values() stripe_utils = StripeUtils() product = cleaned_values['product'] # Check the user has a payment source added stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail']) if not stripe_customer or len(stripe_customer.sources) == 0: return make_return_message('Please register your payment method 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.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) 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 _: # 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': 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 ) product['quantity'] -= 1 client.put('/v1/products/{}'.format(product['uuid']), product) return { 'message': 'Order Successful.', **order_obj } else: logger.error('Could not create plan {}'.format(plan_id)) return make_return_message('Something wrong happened. Contact administrator', 400) 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': uuid4().hex, 'ordered-at': datetime.now().isoformat(), 'product': product['usable-id'], 'one-time-price': one_time_charge, } client.put( '/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']), order_obj ) product['quantity'] -= 1 client.put('/v1/products/{}'.format(product['uuid']), product) return {'message': 'Order successful', **order_obj}, 200 class OrderList(Resource): @staticmethod 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: cleaned_values = validator.get_cleaned_values() orders = client.get_prefix('/v1/user/{}/orders'.format(cleaned_values['username'])) orders_dict = { order.value['order-id']: { **order.value } for order in orders } logger.debug('Orders = {}'.format(orders_dict)) 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(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.get('app', 'port', fallback=5000), debug=True) @app.errorhandler(Exception) def handle_exception(e): app.logger.error(e) # pass through HTTP errors if isinstance(e, HTTPException): return e # now you're handling non-HTTP exceptions only return {'message': 'Server Error'}, 500