2020-01-20 07:30:12 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
from flask import Flask, request
|
|
|
|
from flask_restful import Resource, Api
|
2020-02-19 06:59:54 +00:00
|
|
|
from werkzeug.exceptions import HTTPException
|
2020-01-20 07:30:12 +00:00
|
|
|
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
|
|
|
|
)
|
2020-01-27 08:40:57 +00:00
|
|
|
from helper import get_plan_id_from_product, calculate_charges
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ListProducts(Resource):
|
|
|
|
@staticmethod
|
|
|
|
def get():
|
2020-01-27 09:55:26 +00:00
|
|
|
products = client.get_prefix('/v1/products/')
|
2020-01-27 08:40:57 +00:00
|
|
|
products = [
|
|
|
|
product
|
2020-01-27 09:55:26 +00:00
|
|
|
for product in [p.value for p in products]
|
2020-01-27 08:40:57 +00:00
|
|
|
if product['active']
|
|
|
|
]
|
2020-01-20 07:30:12 +00:00
|
|
|
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():
|
2020-01-27 08:40:57 +00:00
|
|
|
data = request.get_json(silent=True) or {}
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
try:
|
2020-01-27 08:40:57 +00:00
|
|
|
logger.debug('Got data: {}'.format(str(data)))
|
2020-01-20 07:30:12 +00:00
|
|
|
validator = AddProductSchema(data)
|
|
|
|
validator.is_valid()
|
|
|
|
except ValidationException as err:
|
|
|
|
return make_return_message(err, 400)
|
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
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.')
|
2020-01-20 07:30:12 +00:00
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
product_uuid = previous_product.pop('uuid')
|
|
|
|
else:
|
|
|
|
product_uuid = uuid4().hex
|
|
|
|
|
|
|
|
product_value = cleaned_values['specs']
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
product_key = '/v1/products/{}'.format(product_uuid)
|
|
|
|
product_value['uuid'] = product_uuid
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
logger.debug('Adding product data: {}'.format(str(product_value)))
|
2020-01-27 09:55:26 +00:00
|
|
|
client.put(product_key, product_value)
|
2020-01-27 08:40:57 +00:00
|
|
|
if not previous_product:
|
|
|
|
return make_return_message('Product created.')
|
2020-01-20 07:30:12 +00:00
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
return make_return_message('Product updated.')
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class UserRegisterPayment(Resource):
|
|
|
|
@staticmethod
|
|
|
|
def post():
|
2020-01-27 08:40:57 +00:00
|
|
|
data = request.get_json(silent=True) or {}
|
|
|
|
|
2020-01-20 07:30:12 +00:00
|
|
|
try:
|
2020-01-27 08:40:57 +00:00
|
|
|
logger.debug('Got data: {}'.format(str(data)))
|
2020-01-20 07:30:12 +00:00
|
|
|
validator = UserRegisterPaymentSchema(data)
|
|
|
|
validator.is_valid()
|
|
|
|
except ValidationException as err:
|
|
|
|
return make_return_message(err, 400)
|
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
cleaned_values = validator.get_cleaned_values()
|
2020-01-20 07:30:12 +00:00
|
|
|
last4 = data['card_number'].strip()[-4:]
|
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
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))
|
2020-01-20 07:30:12 +00:00
|
|
|
token_response = stripe_utils.get_token_from_card(
|
2020-01-27 08:40:57 +00:00
|
|
|
data['card_number'], data['cvc'], data['expiry_month'],
|
|
|
|
data['expiry_year']
|
2020-01-20 07:30:12 +00:00
|
|
|
)
|
|
|
|
if token_response['response_object']:
|
|
|
|
logger.debug('Token {}'.format(token_response['response_object'].id))
|
2020-01-27 08:40:57 +00:00
|
|
|
resp = stripe_utils.associate_customer_card(
|
|
|
|
stripe_customer.id, token_response['response_object'].id
|
2020-01-20 07:30:12 +00:00
|
|
|
)
|
2020-01-27 08:40:57 +00:00
|
|
|
if resp['response_object']:
|
2020-01-20 07:30:12 +00:00
|
|
|
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)
|
2020-01-27 08:40:57 +00:00
|
|
|
else:
|
|
|
|
return make_return_message('Error: {}'.format(ce_response['error']), 400)
|
2020-01-20 07:30:12 +00:00
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
# 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)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ProductOrder(Resource):
|
|
|
|
@staticmethod
|
|
|
|
def post():
|
2020-01-27 08:40:57 +00:00
|
|
|
data = request.get_json(silent=True) or {}
|
|
|
|
|
2020-01-20 07:30:12 +00:00
|
|
|
try:
|
|
|
|
validator = ProductOrderSchema(data)
|
|
|
|
validator.is_valid()
|
|
|
|
except ValidationException as err:
|
|
|
|
return make_return_message(err, 400)
|
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
cleaned_values = validator.get_cleaned_values()
|
|
|
|
stripe_utils = StripeUtils()
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
product = cleaned_values['product']
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
# Check the user has a payment source added
|
|
|
|
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
if not stripe_customer or len(stripe_customer.sources) == 0:
|
|
|
|
return make_return_message('Please register your payment method first.', 400)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
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']
|
|
|
|
)
|
|
|
|
)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 09:55:26 +00:00
|
|
|
with client.client.lock('product-order') as _:
|
2020-01-20 07:30:12 +00:00
|
|
|
# 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 = {
|
2020-01-27 08:40:57 +00:00
|
|
|
'order-id': uuid4().hex,
|
|
|
|
'ordered-at': datetime.now().isoformat(),
|
2020-01-20 07:30:12 +00:00
|
|
|
'product': product['usable-id'],
|
2020-01-27 08:40:57 +00:00
|
|
|
'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']
|
2020-01-27 09:55:26 +00:00
|
|
|
), order_obj
|
2020-01-27 08:40:57 +00:00
|
|
|
)
|
|
|
|
product['quantity'] -= 1
|
2020-01-27 09:55:26 +00:00
|
|
|
client.put('/v1/products/{}'.format(product['uuid']), product)
|
2020-01-27 08:40:57 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
'message': 'Order Successful.',
|
|
|
|
**order_obj
|
2020-01-20 07:30:12 +00:00
|
|
|
}
|
|
|
|
else:
|
|
|
|
logger.error('Could not create plan {}'.format(plan_id))
|
2020-01-27 09:55:26 +00:00
|
|
|
return make_return_message('Something wrong happened. Contact administrator', 400)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
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')
|
2020-01-27 08:40:57 +00:00
|
|
|
return make_return_message('Error subscribing to plan. Details: {}'.format(msg), 400)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
order_obj = {
|
2020-01-27 08:40:57 +00:00
|
|
|
'order-id': uuid4().hex,
|
|
|
|
'ordered-at': datetime.now().isoformat(),
|
2020-01-20 07:30:12 +00:00
|
|
|
'product': product['usable-id'],
|
2020-01-27 08:40:57 +00:00
|
|
|
'one-time-price': one_time_charge,
|
2020-01-20 07:30:12 +00:00
|
|
|
}
|
|
|
|
client.put(
|
2020-01-27 08:40:57 +00:00
|
|
|
'/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']),
|
2020-01-27 09:55:26 +00:00
|
|
|
order_obj
|
2020-01-20 07:30:12 +00:00
|
|
|
)
|
2020-01-27 08:40:57 +00:00
|
|
|
product['quantity'] -= 1
|
2020-01-27 09:55:26 +00:00
|
|
|
client.put('/v1/products/{}'.format(product['uuid']), product)
|
2020-01-27 08:40:57 +00:00
|
|
|
|
|
|
|
return {'message': 'Order successful', **order_obj}, 200
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class OrderList(Resource):
|
|
|
|
@staticmethod
|
2020-01-27 08:40:57 +00:00
|
|
|
def post():
|
|
|
|
data = request.get_json(silent=True) or {}
|
|
|
|
|
2020-01-20 07:30:12 +00:00
|
|
|
try:
|
|
|
|
validator = OrderListSchema(data)
|
|
|
|
validator.is_valid()
|
|
|
|
except ValidationException as err:
|
|
|
|
return make_return_message(err, 400)
|
|
|
|
else:
|
2020-01-27 08:40:57 +00:00
|
|
|
cleaned_values = validator.get_cleaned_values()
|
2020-01-27 09:55:26 +00:00
|
|
|
orders = client.get_prefix('/v1/user/{}/orders'.format(cleaned_values['username']))
|
2020-01-20 07:30:12 +00:00
|
|
|
orders_dict = {
|
2020-01-27 08:40:57 +00:00
|
|
|
order.value['order-id']: {
|
|
|
|
**order.value
|
2020-01-20 07:30:12 +00:00
|
|
|
}
|
|
|
|
for order in orders
|
|
|
|
}
|
|
|
|
logger.debug('Orders = {}'.format(orders_dict))
|
2020-01-27 08:40:57 +00:00
|
|
|
return {'orders': orders_dict}, 200
|
2020-01-20 07:30:12 +00:00
|
|
|
|
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
logger = logging.getLogger()
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
log_formater = logging.Formatter('[%(filename)s:%(lineno)d] %(message)s')
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
stream_logger = logging.StreamHandler()
|
|
|
|
stream_logger.setFormatter(log_formater)
|
2020-01-20 07:30:12 +00:00
|
|
|
|
2020-01-27 08:40:57 +00:00
|
|
|
# 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')
|
|
|
|
|
2020-02-19 06:59:54 +00:00
|
|
|
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
|