Compare commits
No commits in common. "master" and "0.5.0" have entirely different histories.
3 changed files with 23 additions and 107 deletions
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -1,36 +0,0 @@
|
|||
# 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
|
||||
12
config.py
12
config.py
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
|
||||
from etcd3_wrapper import Etcd3Wrapper
|
||||
from decouple import config, UndefinedValueError
|
||||
from decouple import config
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
|
@ -11,11 +11,7 @@ logging.basicConfig(
|
|||
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)
|
||||
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)
|
||||
STRIPE_API_PRIVATE_KEY = config("STRIPE_API_PRIVATE_KEY")
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import binascii
|
|||
import json
|
||||
|
||||
import requests
|
||||
from decouple import config, Csv, UndefinedValueError
|
||||
from decouple import config, Csv
|
||||
from datetime import datetime
|
||||
from flask import Flask, request
|
||||
from flask_restful import Resource, Api
|
||||
|
|
@ -16,16 +16,6 @@ import time
|
|||
app = Flask(__name__)
|
||||
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):
|
||||
try:
|
||||
|
|
@ -42,54 +32,48 @@ def check_otp(name, realm, token):
|
|||
|
||||
response = requests.post(
|
||||
"{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
|
||||
OTP_SERVER=OTP_SERVER,
|
||||
OTP_VERIFY_ENDPOINT=OTP_VERIFY_ENDPOINT,
|
||||
OTP_SERVER=config("OTP_SERVER", ""),
|
||||
OTP_VERIFY_ENDPOINT=config("OTP_VERIFY_ENDPOINT", "verify/"),
|
||||
),
|
||||
json=data,
|
||||
data=data,
|
||||
)
|
||||
return response.status_code
|
||||
|
||||
|
||||
def get_plan_id_from_product(product):
|
||||
plan_id = "ucloud-v1-"
|
||||
plan_id += product["name"].strip().replace(' ', '-') + "-"
|
||||
plan_id += product["type"]
|
||||
return plan_id
|
||||
|
||||
|
||||
def get_order_id():
|
||||
order_id_kv = client.get("/v1/last_order_id")
|
||||
if order_id_kv is not None:
|
||||
order_id = int(order_id_kv.value) + 1
|
||||
else:
|
||||
order_id = INIT_ORDER_ID
|
||||
order_id = config("INIT_ORDER_ID")
|
||||
client.put("/v1/last_order_id", str(order_id))
|
||||
return "OR-{}".format(order_id)
|
||||
|
||||
|
||||
def get_pricing(price_in_chf_cents, product_type, recurring_period):
|
||||
if product_type == "recurring":
|
||||
return "CHF {}/{}".format(
|
||||
return "CHF {}/ {}".format(
|
||||
price_in_chf_cents/100,
|
||||
recurring_period
|
||||
)
|
||||
elif product_type == "one-time":
|
||||
return "CHF {} (One time charge)".format(price_in_chf_cents/100)
|
||||
|
||||
return "CHF {}".format(price_in_chf_cents/100)
|
||||
|
||||
def get_user_friendly_product(product_dict):
|
||||
uf_product = {
|
||||
return {
|
||||
"name": product_dict["name"],
|
||||
"description": product_dict["description"],
|
||||
"product_id": product_dict["product_id"],
|
||||
"pricing": get_pricing(product_dict["price"], product_dict["type"],
|
||||
product_dict["recurring_period"])
|
||||
}
|
||||
if product_dict["type"] == "recurring":
|
||||
uf_product["minimum_subscription_period"] = (
|
||||
product_dict["recurring_period"]),
|
||||
"minimum_subscription_period":
|
||||
product_dict["minimum_subscription_period"]
|
||||
)
|
||||
return uf_product
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ListProducts(Resource):
|
||||
|
|
@ -110,6 +94,7 @@ class AddProduct(Resource):
|
|||
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(
|
||||
|
|
@ -125,7 +110,7 @@ class AddProduct(Resource):
|
|||
product_uuid = uuid4().hex
|
||||
product_key = "/v1/products/{}".format(product_uuid)
|
||||
product_value = {
|
||||
"product_id": product_uuid,
|
||||
"product_id": product_key,
|
||||
"name": data["product_name"],
|
||||
"description": data["product_description"],
|
||||
"type": data["product_type"],
|
||||
|
|
@ -141,7 +126,7 @@ class AddProduct(Resource):
|
|||
client.put(product_key, product_value, value_in_json=True)
|
||||
return {"message":
|
||||
"Product {} created. Product ID = {}".format(
|
||||
data['product_name'], product_uuid
|
||||
data['product_name'], product_key
|
||||
)}, 200
|
||||
except KeyError as ke:
|
||||
logging.error("KeyError occurred. details = {}".format(str(ke)))
|
||||
|
|
@ -366,38 +351,6 @@ class ProductOrder(Resource):
|
|||
"Product {} is one-time "
|
||||
"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:
|
||||
logging.error("Key error occurred")
|
||||
logging.error(str(key_error))
|
||||
|
|
@ -418,11 +371,14 @@ class OrderList(Resource):
|
|||
orders_dict = {}
|
||||
for p in orders:
|
||||
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"]).strftime("%c")
|
||||
order_dict["product"] = get_user_friendly_product(
|
||||
order_dict["product"])
|
||||
orders_dict[order_dict["order_id"]] = order_dict
|
||||
orders_dict[p.key] = order_dict
|
||||
logging.debug("Orders = {}".format(orders_dict))
|
||||
return orders_dict, 200
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue