Compare commits

...

11 commits

Author SHA1 Message Date
0ea2a68142 Fixed order in which AUTH Token is generated 2019-12-03 18:45:45 +05:00
PCoder
4c916072c3 Update Changelog 2019-10-13 09:17:38 +05:30
PCoder
1897cc087c Handle config not set cases more gracefully 2019-10-13 09:08:40 +05:30
PCoder
ab1714e733 Update Changelog 2019-09-19 22:00:54 +05:30
PCoder
8da25be8b8 Show minimum_subscription_period only for recurring products 2019-09-19 21:24:33 +05:30
PCoder
72d651ea76 Improve the price shown to user 2019-09-19 21:11:42 +05:30
PCoder
671ec75584 Handle one-time payment 2019-09-19 21:08:20 +05:30
PCoder
4c31e40411 Update changelog 2019-09-19 17:32:05 +05:30
PCoder
ffda97ca8c Remove unwanted logging + improve the order key 2019-09-19 17:23:12 +05:30
PCoder
3dab0efcd6 Correct product_id
It was containing the entire dir /v1/...
2019-09-19 17:13:42 +05:30
PCoder
dd5da3b234 Add Changelog 2019-09-19 16:15:39 +05:30
3 changed files with 107 additions and 23 deletions

36
CHANGELOG.md Normal file
View file

@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
[//]: <> (Unreleased)
## [0.5.3] - 2019-10-13
### Added
Handle errors when undefined configs more gracefully
## [0.5.2] - 2019-09-19
### Added
Handling one time payment option for one-time products
## [0.5.1] - 2019-09-19
### Changed
Fix minor issues in 0.5.0
## [0.5.0] - 2019-09-19
### Added
Features described in [\#7125](https://redmine.ungleich.ch/issues/7125)
- Users (inlcuding anonymous users) can list products
- Authenticated users (with valid credentials) can:
- register a payment method
- make an order
- list their orders
- Admin user can create products

View file

@ -1,7 +1,7 @@
import logging import logging
from etcd3_wrapper import Etcd3Wrapper from etcd3_wrapper import Etcd3Wrapper
from decouple import config from decouple import config, UndefinedValueError
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,
@ -11,7 +11,11 @@ logging.basicConfig(
datefmt='%Y-%m-%d:%H:%M:%S', datefmt='%Y-%m-%d:%H:%M:%S',
) )
# load configs
etcd_client = Etcd3Wrapper(host=config("ETCD_HOST"), port=config("ETCD_PORT"))
APP_PORT = config("APP_PORT", 5000) APP_PORT = config("APP_PORT", 5000)
STRIPE_API_PRIVATE_KEY = config("STRIPE_API_PRIVATE_KEY") try:
etcd_client = Etcd3Wrapper(host=config("ETCD_HOST"), port=config("ETCD_PORT"))
STRIPE_API_PRIVATE_KEY = config("STRIPE_API_PRIVATE_KEY")
except UndefinedValueError as uve:
print(str(uve))
exit(1)

View file

@ -2,7 +2,7 @@ import binascii
import json import json
import requests import requests
from decouple import config, Csv from decouple import config, Csv, UndefinedValueError
from datetime import datetime from datetime import datetime
from flask import Flask, request from flask import Flask, request
from flask_restful import Resource, Api from flask_restful import Resource, Api
@ -16,6 +16,16 @@ import time
app = Flask(__name__) app = Flask(__name__)
api = Api(app) api = Api(app)
# load configs
OTP_SERVER = config("OTP_SERVER", "")
OTP_VERIFY_ENDPOINT = config("OTP_VERIFY_ENDPOINT", "verify/")
try:
INIT_ORDER_ID = config("INIT_ORDER_ID")
REALM_ALLOWED = config("REALM_ALLOWED", cast=Csv(str))
except UndefinedValueError as uve:
print(str(uve))
exit(1)
def check_otp(name, realm, token): def check_otp(name, realm, token):
try: try:
@ -32,48 +42,54 @@ def check_otp(name, realm, token):
response = requests.post( response = requests.post(
"{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( "{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
OTP_SERVER=config("OTP_SERVER", ""), OTP_SERVER=OTP_SERVER,
OTP_VERIFY_ENDPOINT=config("OTP_VERIFY_ENDPOINT", "verify/"), OTP_VERIFY_ENDPOINT=OTP_VERIFY_ENDPOINT,
), ),
data=data, json=data,
) )
return response.status_code return response.status_code
def get_plan_id_from_product(product): def get_plan_id_from_product(product):
plan_id = "ucloud-v1-" plan_id = "ucloud-v1-"
plan_id += product["name"].strip().replace(' ', '-') + "-" plan_id += product["name"].strip().replace(' ', '-') + "-"
plan_id += product["type"] plan_id += product["type"]
return plan_id return plan_id
def get_order_id(): def get_order_id():
order_id_kv = client.get("/v1/last_order_id") order_id_kv = client.get("/v1/last_order_id")
if order_id_kv is not None: if order_id_kv is not None:
order_id = int(order_id_kv.value) + 1 order_id = int(order_id_kv.value) + 1
else: else:
order_id = config("INIT_ORDER_ID") order_id = INIT_ORDER_ID
client.put("/v1/last_order_id", str(order_id)) client.put("/v1/last_order_id", str(order_id))
return "OR-{}".format(order_id) return "OR-{}".format(order_id)
def get_pricing(price_in_chf_cents, product_type, recurring_period): def get_pricing(price_in_chf_cents, product_type, recurring_period):
if product_type == "recurring": if product_type == "recurring":
return "CHF {}/ {}".format( return "CHF {}/{}".format(
price_in_chf_cents/100, price_in_chf_cents/100,
recurring_period recurring_period
) )
elif product_type == "one-time": elif product_type == "one-time":
return "CHF {}".format(price_in_chf_cents/100) return "CHF {} (One time charge)".format(price_in_chf_cents/100)
def get_user_friendly_product(product_dict): def get_user_friendly_product(product_dict):
return { uf_product = {
"name": product_dict["name"], "name": product_dict["name"],
"description": product_dict["description"], "description": product_dict["description"],
"product_id": product_dict["product_id"], "product_id": product_dict["product_id"],
"pricing": get_pricing(product_dict["price"], product_dict["type"], "pricing": get_pricing(product_dict["price"], product_dict["type"],
product_dict["recurring_period"]), product_dict["recurring_period"])
"minimum_subscription_period":
product_dict["minimum_subscription_period"]
} }
if product_dict["type"] == "recurring":
uf_product["minimum_subscription_period"] = (
product_dict["minimum_subscription_period"]
)
return uf_product
class ListProducts(Resource): class ListProducts(Resource):
@ -94,7 +110,6 @@ class AddProduct(Resource):
def post(): def post():
data = request.json data = request.json
logging.debug("Got data: {}".format(str(data))) logging.debug("Got data: {}".format(str(data)))
REALM_ALLOWED = config("REALM_ALLOWED", cast=Csv(str))
logging.debug("REALM_ALLOWED = {}".format(REALM_ALLOWED)) logging.debug("REALM_ALLOWED = {}".format(REALM_ALLOWED))
if data["realm"] not in REALM_ALLOWED: if data["realm"] not in REALM_ALLOWED:
logging.error( logging.error(
@ -110,7 +125,7 @@ class AddProduct(Resource):
product_uuid = uuid4().hex product_uuid = uuid4().hex
product_key = "/v1/products/{}".format(product_uuid) product_key = "/v1/products/{}".format(product_uuid)
product_value = { product_value = {
"product_id": product_key, "product_id": product_uuid,
"name": data["product_name"], "name": data["product_name"],
"description": data["product_description"], "description": data["product_description"],
"type": data["product_type"], "type": data["product_type"],
@ -126,7 +141,7 @@ class AddProduct(Resource):
client.put(product_key, product_value, value_in_json=True) client.put(product_key, product_value, value_in_json=True)
return {"message": return {"message":
"Product {} created. Product ID = {}".format( "Product {} created. Product ID = {}".format(
data['product_name'], product_key data['product_name'], product_uuid
)}, 200 )}, 200
except KeyError as ke: except KeyError as ke:
logging.error("KeyError occurred. details = {}".format(str(ke))) logging.error("KeyError occurred. details = {}".format(str(ke)))
@ -351,6 +366,38 @@ class ProductOrder(Resource):
"Product {} is one-time " "Product {} is one-time "
"payment".format(product_obj["type"]) "payment".format(product_obj["type"])
) )
charge_response = stripe_utils.make_charge(
amount=product_obj['price'],
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')
logging.error("Could not make a one time payment")
logging.error("Details = {}".format(msg))
return {"message": "Error subscribing to plan. "
"Details: {}".format(msg)}, 400
order_obj = {
"order_id": get_order_id(),
"ordered_at": int(time.time()),
"product": product_obj,
}
client.put("/v1/user/{}/orders".format(
data['name']), json.dumps(order_obj),
value_in_json=True)
order_obj["ordered_at"] = datetime.fromtimestamp(
order_obj["ordered_at"]).strftime("%c")
order_obj["product"] = get_user_friendly_product(
product_obj
)
logging.debug(str(order_obj))
return {"message": "Order successful",
"order_details": order_obj}, 200
except KeyError as key_error: except KeyError as key_error:
logging.error("Key error occurred") logging.error("Key error occurred")
logging.error(str(key_error)) logging.error(str(key_error))
@ -371,14 +418,11 @@ class OrderList(Resource):
orders_dict = {} orders_dict = {}
for p in orders: for p in orders:
order_dict = json.loads(p.value) order_dict = json.loads(p.value)
logging.debug("order_dict = " + str(order_dict))
logging.debug("type p.value = " + str(type(p.value)))
logging.debug("p.value = " + str(p.value))
order_dict["ordered_at"] = datetime.fromtimestamp( order_dict["ordered_at"] = datetime.fromtimestamp(
order_dict["ordered_at"]).strftime("%c") order_dict["ordered_at"]).strftime("%c")
order_dict["product"] = get_user_friendly_product( order_dict["product"] = get_user_friendly_product(
order_dict["product"]) order_dict["product"])
orders_dict[p.key] = order_dict orders_dict[order_dict["order_id"]] = order_dict
logging.debug("Orders = {}".format(orders_dict)) logging.debug("Orders = {}".format(orders_dict))
return orders_dict, 200 return orders_dict, 200