-
Notifications
You must be signed in to change notification settings - Fork 0
/
payment.py
187 lines (154 loc) · 5.94 KB
/
payment.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import instana
import os
import sys
import time
import logging
import uuid
import json
import requests
import traceback
import opentracing as ot
import opentracing.ext.tags as tags
from flask import Flask
from flask import Response
from flask import request
from flask import jsonify
from rabbitmq import Publisher
# Prometheus
import prometheus_client
from prometheus_client import Counter, Histogram
app = Flask(__name__)
app.logger.setLevel(logging.INFO)
CART = os.getenv('CART_HOST', 'cart')
CART_PORT = os.getenv('CART_PORT', 8080)
USER = os.getenv('USER_HOST', 'user')
USER_PORT = os.getenv('USER_PORT', 8080)
PAYMENT_GATEWAY = os.getenv('PAYMENT_GATEWAY', 'https://google.com/')
# Prometheus
PromMetrics = {}
PromMetrics['SOLD_COUNTER'] = Counter('sold_count', 'Running count of items sold')
PromMetrics['AUS'] = Histogram('units_sold', 'Avergae Unit Sale', buckets=(1, 2, 5, 10, 100))
PromMetrics['AVS'] = Histogram('cart_value', 'Avergae Value Sale', buckets=(100, 200, 500, 1000, 2000, 5000, 10000))
@app.errorhandler(Exception)
def exception_handler(err):
app.logger.error(str(err))
return str(err), 500
@app.route('/health', methods=['GET'])
def health():
return 'OK'
# Prometheus
@app.route('/metrics', methods=['GET'])
def metrics():
res = []
for m in PromMetrics.values():
res.append(prometheus_client.generate_latest(m))
return Response(res, mimetype='text/plain')
@app.route('/pay/<id>', methods=['POST'])
def pay(id):
app.logger.info('payment for {}'.format(id))
cart = request.get_json()
app.logger.info(cart)
anonymous_user = True
# add some log info to the active trace
span = ot.tracer.active_span
span.log_kv({'id': id})
span.log_kv({'cart': cart})
# check user exists
try:
req = requests.get('http://{user}:{userPort}/check/{id}'.format(user=USER, userPort=USER_PORT, id=id))
except requests.exceptions.RequestException as err:
app.logger.error(err)
return str(err), 500
if req.status_code == 200:
anonymous_user = False
# check that the cart is valid
# this will blow up if the cart is not valid
has_shipping = False
for item in cart.get('items'):
if item.get('sku') == 'SHIP':
has_shipping = True
if cart.get('total', 0) == 0 or has_shipping == False:
app.logger.warn('cart not valid')
return 'cart not valid', 400
# dummy call to payment gateway, hope they dont object
try:
req = requests.get(PAYMENT_GATEWAY)
app.logger.info('{} returned {}'.format(PAYMENT_GATEWAY, req.status_code))
except requests.exceptions.RequestException as err:
app.logger.error(err)
return str(err), 500
if req.status_code != 200:
return 'payment error', req.status_code
# Prometheus
# items purchased
item_count = countItems(cart.get('items', []))
PromMetrics['SOLD_COUNTER'].inc(item_count)
PromMetrics['AUS'].observe(item_count)
PromMetrics['AVS'].observe(cart.get('total', 0))
# Generate order id
orderid = str(uuid.uuid4())
queueOrder({ 'orderid': orderid, 'user': id, 'cart': cart })
# add to order history
if not anonymous_user:
try:
req = requests.post('http://{user}:{userPort}/order/{id}'.format(user=USER, userPort=USER_PORT, id=id),
data=json.dumps({'orderid': orderid, 'cart': cart}),
headers={'Content-Type': 'application/json'})
app.logger.info('order history returned {}'.format(req.status_code))
except requests.exceptions.RequestException as err:
app.logger.error(err)
return str(err), 500
# delete cart
try:
req = requests.delete('http://{cart}:{cartPort}/cart/{id}'.format(cart=CART, cartPort=CART_PORT, id=id));
app.logger.info('cart delete returned {}'.format(req.status_code))
except requests.exceptions.RequestException as err:
app.logger.error(err)
return str(err), 500
if req.status_code != 200:
return 'order history update error', req.status_code
return jsonify({ 'orderid': orderid })
def queueOrder(order):
app.logger.info('queue order')
# RabbitMQ pika is not currently traced automatically
# opentracing tracer is automatically set to Instana tracer
# start a span
parent_span = ot.tracer.active_span
with ot.tracer.start_active_span('queueOrder', child_of=parent_span,
tags={
'exchange': Publisher.EXCHANGE,
'key': Publisher.ROUTING_KEY
}) as tscope:
tscope.span.set_tag('span.kind', 'intermediate')
tscope.span.log_kv({'orderid': order.get('orderid')})
with ot.tracer.start_active_span('rabbitmq', child_of=tscope.span,
tags={
'exchange': Publisher.EXCHANGE,
'sort': 'publish',
'address': Publisher.HOST,
'key': Publisher.ROUTING_KEY
}
) as scope:
# For screenshot demo requirements optionally add in a bit of delay
delay = int(os.getenv('PAYMENT_DELAY_MS', 0))
time.sleep(delay / 1000)
headers = {}
ot.tracer.inject(scope.span.context, ot.Format.HTTP_HEADERS, headers)
app.logger.info('msg headers {}'.format(headers))
publisher.publish(order, headers)
def countItems(items):
count = 0
for item in items:
if item.get('sku') != 'SHIP':
count += item.get('qty')
return count
# RabbitMQ
publisher = Publisher(app.logger)
if __name__ == "__main__":
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.INFO)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
app.logger.info('Payment gateway {}'.format(PAYMENT_GATEWAY))
port = int(os.getenv("SHOP_PAYMENT_PORT", "8080"))
app.logger.info('Starting on port {}'.format(port))
app.run(host='0.0.0.0', port=port)