import binascii import json import requests from decouple import config, Csv from flask import Flask, request from flask_restful import Resource, Api from pyotp import TOTP from config import etcd_client as client, logging, APP_PORT from stripe_utils import StripeUtils from uuid import uuid4 app = Flask(__name__) api = Api(app) def check_otp(name, realm, token): try: data = { "auth_name": config("AUTH_NAME", ""), "auth_token": TOTP(config("AUTH_SEED", "")).now(), "auth_realm": config("AUTH_REALM", ""), "name": name, "realm": realm, "token": token, } except binascii.Error: return 400 response = requests.post( "{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( OTP_SERVER=config("OTP_SERVER", ""), OTP_VERIFY_ENDPOINT=config("OTP_VERIFY_ENDPOINT", "verify/"), ), data=data, ) return response.status_code class ListProducts(Resource): @staticmethod def get(): products = client.get_prefix("/v1/products/", value_in_json=False) prod_dict = {} for p in products: prod_dict[p.key] = json.loads(p.value) logging.debug("Products = {}".format(prod_dict)) return prod_dict, 200 class AddProduct(Resource): @staticmethod def post(): data = request.json logging.debug("Got data: {}".format(str(data))) REALM_ALLOWED = config("REALM_ALLOWED", cast=Csv(str)) logging.debug("REALM_ALLOWED = {}".format(REALM_ALLOWED)) if data["realm"] not in REALM_ALLOWED: logging.error( "The given realm {} is not " "allowed to do add product".format(data["realm"])) return {"message": "Forbidden"}, 403 otp_response = check_otp(data["name"], data["realm"], data["token"]) if otp_response != 200: return {"message": "Wrong Credentials"}, 403 try: product_uuid = uuid4().hex product_key = "/v1/products/{}".format(product_uuid) product_value = { "name": data["product_name"], "description": data["product_description"], "type": data["product_type"], "price": data["product_price"], "recurring_duration": data["product_recurring_duration"], "recurring_duration_units": data["product_recurring_duration_units"] } logging.debug("Adding product data: {}".format(str(product_value))) client.put(product_key, product_value, value_in_json=True) return {"message": "Product {} created".format(data['product_name'])}, 200 except KeyError as ke: logging.error("KeyError occurred. details = {}".format(str(ke))) return {"message": "Missing or wrong parameters"}, 400 class UserRegisterPayment(Resource): @staticmethod def get_token(card_number, cvc, exp_month, exp_year): stripe_utils = StripeUtils() token_response = stripe_utils.get_token_from_card( card_number, cvc, exp_month, exp_year ) if token_response["response_object"]: return token_response["response_object"].id else: return None @staticmethod def post(): try: data = request.json logging.debug("Got data: {}".format(str(data))) otp_response = check_otp(data["name"], data["realm"], data["token"]) last4 = data['card_number'].strip()[-4:] if otp_response != 200: return {"message": "Wrong Credentials"}, 403 stripe_utils = StripeUtils() # Does customer already exist ? stripe_customer = stripe_utils.get_stripe_customer_from_email( data["email"]) # Does customer already exist ? if stripe_customer is not None: logging.debug( "Customer {} exists already".format(data['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) logging.debug(message) return { "message": message }, 400 elif ce_response["response_object"] is False: # Associate card with user logging.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"]: logging.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 {"message": "Card ending in {} registered as your payment " "source".format(last4) }, 200 else: logging.error("Could not obtain token") return {"message": "Error with payment gateway. " "Contact support"}, 400 else: logging.error( "Error occurred {}".format(ce_response["error"]) ) return {"message": "Error: {}".format( ce_response["error"] )}, 400 else: # Stripe customer does not exist, create a new one logging.debug( "Customer {} does not exist, " "creating new".format(data['email']) ) token_response = stripe_utils.get_token_from_card( data["card_number"], data["cvc"], data["expiry_month"], data["expiry_year"] ) if token_response["response_object"]: logging.debug( "Token {}".format( token_response["response_object"].id)) #Create stripe customer stripe_customer_resp = stripe_utils.create_customer( name=data["card_holder_name"], token=token_response["response_object"].id, email=data["email"] ) if stripe_customer_resp["response_object"]: logging.debug( "Created stripe customer {}".format( stripe_customer_resp["response_object"].id ) ) stripe_customer = stripe_customer_resp[ "response_object"] return {"message": "Card ending in {} registered as your payment " "source".format(last4) }, 200 else: logging.error("Could not get/create stripe_customer " "for {}".format(data["email"])) return {"message": "Error with card. Contact support"}, 400 else: logging.error("Could not obtain token") return {"message": "Error with payment gateway. " "Contact support"}, 400 except KeyError as key_error: logging.error("Key error occurred") logging.error(str(key_error)) return {"message": "Missing or wrong parameters"}, 400 class ProductOrder(Resource): @staticmethod def post(): data = request.json try: otp_response = check_otp(data["name"], data["realm"], data["token"]) if otp_response != 200: return {"message": "Wrong Credentials"}, 403 # Validate the given product is ok product_id = data["product_id"] product = client.get( "/v1/products/{}".format(product_id), value_in_json=True ) if not product: logging.debug("User chose invalid product {}".format(product_id)) return {"message": "Invalid product"}, 400 logging.debug("Got product {}: {}".format(product.key, product.value)) # Check the user has a payment source added # Initiate a one-time/subscription based on product type # If charge successful, create an order object # Else handle unsuccessful cases with grace except KeyError as key_error: logging.error("Key error occurred") logging.error(str(key_error)) return {"message": "Missing or wrong parameters"}, 400 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") if __name__ == '__main__': app.run(host="::", port=APP_PORT, debug=True)