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
from etcd3_wrapper import Etcd3Wrapper
from decouple import config
from decouple import config, UndefinedValueError
logging.basicConfig(
level=logging.DEBUG,
@ -11,7 +11,11 @@ logging.basicConfig(
datefmt='%Y-%m-%d:%H:%M:%S',
)
etcd_client = Etcd3Wrapper(host=config("ETCD_HOST"), port=config("ETCD_PORT"))
# load configs
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 requests
from decouple import config, Csv
from decouple import config, Csv, UndefinedValueError
from datetime import datetime
from flask import Flask, request
from flask_restful import Resource, Api
@ -16,6 +16,16 @@ 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:
@ -32,48 +42,54 @@ def check_otp(name, realm, token):
response = requests.post(
"{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
OTP_SERVER=config("OTP_SERVER", ""),
OTP_VERIFY_ENDPOINT=config("OTP_VERIFY_ENDPOINT", "verify/"),
OTP_SERVER=OTP_SERVER,
OTP_VERIFY_ENDPOINT=OTP_VERIFY_ENDPOINT,
),
data=data,
json=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 = config("INIT_ORDER_ID")
order_id = 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 {}".format(price_in_chf_cents/100)
return "CHF {} (One time charge)".format(price_in_chf_cents/100)
def get_user_friendly_product(product_dict):
return {
uf_product = {
"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"]),
"minimum_subscription_period":
product_dict["minimum_subscription_period"]
product_dict["recurring_period"])
}
if product_dict["type"] == "recurring":
uf_product["minimum_subscription_period"] = (
product_dict["minimum_subscription_period"]
)
return uf_product
class ListProducts(Resource):
@ -94,7 +110,6 @@ 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(
@ -110,7 +125,7 @@ class AddProduct(Resource):
product_uuid = uuid4().hex
product_key = "/v1/products/{}".format(product_uuid)
product_value = {
"product_id": product_key,
"product_id": product_uuid,
"name": data["product_name"],
"description": data["product_description"],
"type": data["product_type"],
@ -126,7 +141,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_key
data['product_name'], product_uuid
)}, 200
except KeyError as ke:
logging.error("KeyError occurred. details = {}".format(str(ke)))
@ -351,6 +366,38 @@ 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))
@ -371,14 +418,11 @@ 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[p.key] = order_dict
orders_dict[order_dict["order_id"]] = order_dict
logging.debug("Orders = {}".format(orders_dict))
return orders_dict, 200