async def webhook(payload: dict = Body(...), X_CC_Webhook_Signature: str = Header(None), User_Agent: str = Header(None)): try: if User_Agent != 'weipay-webhooks': raise UnauthorizedError try: event = Webhook.construct_event(dumps(payload).encode(), X_CC_Webhook_Signature, CoinbaseCommerceSettings.SHARED_SECRET) except (WebhookInvalidPayload, SignatureVerificationError): raise UnauthorizedError order = Order.objects.get(id=event.data.metadata.order) if event.type == 'charge:pending': if not order.stockRemoved: if price_service.remove_stock(order): order.orderStatus = 'placed' order.stockRemoved = True else: order.orderStatus = 'to refund' elif event.type == 'charge:confirmed': order.orderStatus = 'paid' elif event.type == 'charge:failed': order.orderStatus = 'failed' if order.stockRemoved: price_service.add_stock(order) order.stockRemoved = False elif event.type == 'charge:delayed': order.status = 'to refund' # TODO: send an email to the user that they payed after expiratin, that the order failed, and they will be refunded (minus transaction fees perhaps) order.save() return 'ok' except UnauthorizedError: raise UnauthorizedError().http_exception except Exception as e: raise e
def webhooks(): # event payload request_data = request.data.decode('utf-8') # webhook signature request_sig = request.headers.get('X-CC-Webhook-Signature', None) try: # signature verification and event object construction event = Webhook.construct_event(request_data, request_sig, WEBHOOK_SECRET) except (WebhookInvalidPayload, SignatureVerificationError) as e: return str(e), 400 print("Received event: id={id}, type={type}".format(id=event.id, type=event.type)) print(event) order = Order.query.filter_by(id=event.data.id).first() if order: order.status = event.type order.updated_at = datetime.utcnow() save_changes(order) if event.type == 'charge:confirmed' or event.type == 'charge:resolved': user = User.query.filter_by(email=event.data.metadata.email).first() if event.data.name == '1.000 Credits': modify_credit_balance(user, 1000, 'Bought 1.000 Credits') elif event.data.name == '5.000 Credits': modify_credit_balance(user, 5000, 'Bought 5.000 Credits') assign_role_to_user('free_download', user) elif event.data.name == '10.000 Credits': modify_credit_balance(user, 10000, 'Bought 10.000 Credits') assign_role_to_user('free_download', user) assign_role_to_user('realtime_access', user) elif event.data.name == '50.000 Credits': modify_credit_balance(user, 50000, 'Bought 50.000 Credits') assign_role_to_user('free_download', user) assign_role_to_user('realtime_access', user) elif event.data.name == '100.000 Credits': modify_credit_balance(user, 100000, 'Bought 100.000 Credits') assign_role_to_user('free_download', user) assign_role_to_user('free_delete_user', user) assign_role_to_user('realtime_access', user) if not order and event.type == 'charge:created': order = Order( id=event.data.id, status=event.type, created_at=datetime.strptime(event.data.created_at, '%Y-%m-%dT%H:%M:%SZ'), item=event.data.name, ) if event.data.name == 'chatpic.exposed': order.email = '*****@*****.**' return 'success', 200 else: order.email = event.data.metadata.email save_changes(order) #send_mail(order.email,order, event) return 'success', 200
def webhooks(): request_data = request.data.decode('utf-8') request_sig = request.headers.get('X-CC-Webhook-Signature', None) try: event = Webhook.construct_event(request_data, request_sig, WEBHOOK_SECRET) except (WebhookInvalidPayload, SignatureVerificationError) as e: return str(e), 400 print("Received event: id={id}, type={type}".format(id=event.id, type=event.type)) return 'success', 200
def coinbase_webhook(): # event payload request_data = request.data.decode("utf-8") # webhook signature request_sig = request.headers.get("X-CC-Webhook-Signature", None) try: # signature verification and event object construction event = Webhook.construct_event(request_data, request_sig, COINBASE_WEBHOOK_SECRET) except (WebhookInvalidPayload, SignatureVerificationError) as e: LOG.exception("Invalid Coinbase webhook") return str(e), 400 LOG.d("Coinbase event %s", event) if event["type"] == "charge:confirmed": if handle_coinbase_event(event): return "success", 200 else: return "error", 400 return "success", 200
def test_event_construct(self): event = Webhook.construct_event(PAYLOAD_STR, SIG_HEADER, SECRET) self.assertIsInstance(event, Event) self.assertEqual(event.id, '24934862-d980-46cb-9402-43c81b0cdba6')
def test_invalid_payload_no_event(self): payload_without_event = '{"id":1,"scheduled_for":"2017-01-31T20:50:02Z","attempt_number":1}' with self.assertRaises(WebhookInvalidPayload) as context: Webhook.construct_event(payload_without_event, SIG_HEADER, SECRET) self.assertTrue('Invalid payload provided' in str(context.exception))
def test_invalid_payload_not_json(self): invalid_payload_str = 'invalid payload' with self.assertRaises(WebhookInvalidPayload) as context: Webhook.construct_event(invalid_payload_str, SIG_HEADER, SECRET) self.assertTrue('Invalid payload provided' in str(context.exception))
def webhook(): logger.debug('Received HTTP request') if request.method == 'POST': logger.debug('HTTP request is type POST') # event payload request_data = request.data.decode('utf-8') # webhook signature request_sig = request.headers.get('X-CC-Webhook-Signature', None) logger.debug('Validating webhook event signature') # signature verification and event object construction try: event = Webhook.construct_event(request_data, request_sig, config.webhook_secret) except (WebhookInvalidPayload, SignatureVerificationError) as e: return str(e), 400 logger.info("Received event: id={id}, code={code}, type={type}".format(id=event.id, code=event.data.code, type=event.type)) # check event.type. We should only receive charge:confirmed. If webhook is misconfigured we want to ignore pending or failed charges. if event.type != "charge:confirmed": logger.debug('Event is not a confirmed charge. Waiting...') return 'Event received', 200 logger.debug('Processing confirmed charge') # Add all payments and divide by tickets price to get number of tickets purchased # event.data.payments is a list, usually contains one payment, but might have more than one. try: logger.debug('Calculating payment total') payment_total = 0 # add all payments in list for payment in event.data.payments: payment_total += float(payment.value.local.amount) logger.debug('Calculating number of tickets') # Payment should be exact multiple of ticket_price num_tickets = int(payment_total // config.ticket_price) # A confirmed charge should have payment_total > 0 assert(payment_total > 0) # num_tickets should be > 0, if ticket_price is set correctly assert(num_tickets > 0) except Exception as e: logger.error(str(e)) return 'Error processing event: '+str(e), 400 logger.debug("Payment total: " + str(payment_total)) logger.debug("Number of tickets: " + str(num_tickets)) logger.debug('Creating EventBrite discount code '+str(event.data.code)) # Create a dicsount code, based on the Coinbase charge code # Set quantity to num_tickets and discount to 100% eventbrite = Eventbrite(config.eventbrite_token) result = eventbrite.post_event_discount( config.event_id, discount_code=event.data.code, discount_percent_off=100, discount_quantity_available=num_tickets ) # Check result from EventBrite API if 'status_code' in result and result.status_code == 400: logger.error("Error creating EventBrite discount code: "+str(result)) return "Error creating EventBrite discount code", 400 else: logger.info("EventBrite success: "+str(result)) return 'Success', 200