338 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
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.')
 | 
						|
 | 
						|
################################################################################
 | 
						|
# Nico-ok-marker
 | 
						|
 | 
						|
 | 
						|
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
 |