From 441743cb896e7a7065b871ae7a48385a47ebb354 Mon Sep 17 00:00:00 2001 From: AZtheAsian Date: Sat, 3 Dec 2016 10:51:33 -0700 Subject: [PATCH] integrations: Add webhook code, API endpoint, and tests for stripe. This integration still needs documentation. --- static/images/integrations/logos/stripe.png | Bin 0 -> 44919 bytes .../stripe/stripe_charge_dispute_closed.json | 2 +- .../stripe/stripe_charge_dispute_created.json | 2 +- .../stripe/stripe_customer_created_email.json | 41 +++++ zerver/lib/integrations.py | 1 + zerver/tests/webhooks/test_stripe.py | 156 ++++++++++++++++++ zerver/views/webhooks/stripe.py | 126 ++++++++++++++ 7 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 static/images/integrations/logos/stripe.png create mode 100644 zerver/fixtures/stripe/stripe_customer_created_email.json create mode 100644 zerver/tests/webhooks/test_stripe.py create mode 100644 zerver/views/webhooks/stripe.py diff --git a/static/images/integrations/logos/stripe.png b/static/images/integrations/logos/stripe.png new file mode 100644 index 0000000000000000000000000000000000000000..046894bf5fe960c9f805b18d663dc4a0d9bc2909 GIT binary patch literal 44919 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肋PI>#>UK#dCq02XI!Q*b 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 literal 0 HcmV?d00001 diff --git a/zerver/fixtures/stripe/stripe_charge_dispute_closed.json b/zerver/fixtures/stripe/stripe_charge_dispute_closed.json index 80108b0c09..1939d17d36 100644 --- a/zerver/fixtures/stripe/stripe_charge_dispute_closed.json +++ b/zerver/fixtures/stripe/stripe_charge_dispute_closed.json @@ -11,7 +11,7 @@ "object": { "id": "dp_00000000000000", "object": "dispute", - "amount": 1000, + "amount": 1001, "balance_transactions": [], "charge": "ch_00000000000000", "created": 1480672052, diff --git a/zerver/fixtures/stripe/stripe_charge_dispute_created.json b/zerver/fixtures/stripe/stripe_charge_dispute_created.json index 1380260e83..81663cb95b 100644 --- a/zerver/fixtures/stripe/stripe_charge_dispute_created.json +++ b/zerver/fixtures/stripe/stripe_charge_dispute_created.json @@ -15,7 +15,7 @@ "balance_transactions": [], "charge": "ch_00000000000000", "created": 1480672043, - "currency": "aud", + "currency": "jpy", "evidence": { "access_activity_log": null, "billing_address": null, diff --git a/zerver/fixtures/stripe/stripe_customer_created_email.json b/zerver/fixtures/stripe/stripe_customer_created_email.json new file mode 100644 index 0000000000..daad6ac13a --- /dev/null +++ b/zerver/fixtures/stripe/stripe_customer_created_email.json @@ -0,0 +1,41 @@ +{ + "created": 1326853478, + "livemode": false, + "id": "evt_00000000000000", + "type": "customer.created", + "object": "event", + "request": null, + "pending_webhooks": 1, + "api_version": "2016-07-06", + "data": { + "object": { + "id": "cus_00000000000000", + "object": "customer", + "account_balance": 0, + "created": 1480672088, + "currency": "aud", + "default_source": null, + "delinquent": false, + "description": null, + "discount": null, + "email": "example@abc.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_9fYl7K4F7pQxrY/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_9fYl7K4F7pQxrY/subscriptions" + } + } + } +} diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 9db539e6e6..12612dcbcb 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -127,6 +127,7 @@ WEBHOOK_INTEGRATIONS = [ WebhookIntegration('semaphore'), WebhookIntegration('sentry'), WebhookIntegration('stash'), + WebhookIntegration('stripe', display_name='Stripe'), WebhookIntegration('taiga'), WebhookIntegration('teamcity'), WebhookIntegration('transifex'), diff --git a/zerver/tests/webhooks/test_stripe.py b/zerver/tests/webhooks/test_stripe.py new file mode 100644 index 0000000000..70f6f846ec --- /dev/null +++ b/zerver/tests/webhooks/test_stripe.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +from six import text_type +from zerver.lib.test_classes import WebhookTestCase + +class StripeHookTests(WebhookTestCase): + STREAM_NAME = 'test' + URL_TEMPLATE = "/api/v1/external/stripe?&api_key={api_key}" + FIXTURE_DIR_NAME = 'stripe' + + def test_charge_dispute_closed(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A charge dispute for **10.01aud** has been closed as **won**.\nThe charge in dispute was **[ch_00000000000000](https://dashboard.stripe.com/payments/ch_00000000000000)**." + + # use fixture named stripe_charge_dispute_closed + self.send_and_test_stream_message('charge_dispute_closed', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_charge_dispute_created(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A charge dispute for **1000jpy** has been created.\nThe charge in dispute is **[ch_00000000000000](https://dashboard.stripe.com/payments/ch_00000000000000)**." + + # use fixture named stripe_charge_dispute_created + self.send_and_test_stream_message('charge_dispute_created', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_charge_failed(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A charge with id **[ch_00000000000000](https://dashboard.stripe.com/payments/ch_00000000000000)** for **1.00aud** has failed." + + # use fixture named stripe_charge_failed + self.send_and_test_stream_message('charge_failed', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_charge_succeeded(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A charge with id **[ch_00000000000000](https://dashboard.stripe.com/payments/ch_00000000000000)** for **1.00aud** has succeeded." + + # use fixture named stripe_charge_succeeded + self.send_and_test_stream_message('charge_succeeded', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_created_email(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A new customer with id **[cus_00000000000000](https://dashboard.stripe.com/customers/cus_00000000000000)** and email **example@abc.com** has been created." + + # use fixture named stripe_customer_created_email + self.send_and_test_stream_message('customer_created_email', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_created(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A new customer with id **[cus_00000000000000](https://dashboard.stripe.com/customers/cus_00000000000000)** has been created." + + # use fixture named stripe_customer_created + self.send_and_test_stream_message('customer_created', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_deleted(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A customer with id **[cus_00000000000000](https://dashboard.stripe.com/customers/cus_00000000000000)** has been deleted." + + # use fixture named stripe_customer_deleted + self.send_and_test_stream_message('customer_deleted', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_subscription_created(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"A new customer subscription for **20.00aud** every **month** has been created.\nThe subscription has id **[sub_00000000000000](https://dashboard.stripe.com/subscriptions/sub_00000000000000)**." + + # use fixture named stripe_customer_subscription_created + self.send_and_test_stream_message('customer_subscription_created', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_subscription_deleted(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"The customer subscription with id **[sub_00000000000000](https://dashboard.stripe.com/subscriptions/sub_00000000000000)** was deleted." + + # use fixture named stripe_customer_subscription_deleted + self.send_and_test_stream_message('customer_subscription_deleted', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_customer_subscription_trial_will_end(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"The customer subscription trial with id **[sub_00000000000000](https://dashboard.stripe.com/subscriptions/sub_00000000000000)** will end on Dec 04 2016 at 06:07PM" + + # use fixture named stripe_customer_subscription_trial_will_end + self.send_and_test_stream_message('customer_subscription_trial_will_end', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_invoice_payment_failed(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"An invoice payment on invoice with id **[in_00000000000000](https://dashboard.stripe.com/invoices/in_00000000000000)** and with **0.00aud** due has failed." + + # use fixture named stripe_invoice_payment_failed + self.send_and_test_stream_message('invoice_payment_failed', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_order_payment_failed(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"An order payment on order with id **[or_00000000000000](https://dashboard.stripe.com/orders/or_00000000000000)** for **15.00aud** has failed." + + # use fixture named stripe_order_payment_failed + self.send_and_test_stream_message('order_payment_failed', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_order_payment_succeeded(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"An order payment on order with id **[or_00000000000000](https://dashboard.stripe.com/orders/or_00000000000000)** for **15.00aud** has succeeded." + + # use fixture named stripe_order_payment_succeeded + self.send_and_test_stream_message('order_payment_succeeded', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_order_updated(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"The order with id **[or_00000000000000](https://dashboard.stripe.com/orders/or_00000000000000)** for **15.00aud** has been updated." + + # use fixture named stripe_order_updated + self.send_and_test_stream_message('order_updated', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_transfer_failed(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"The transfer with description **Transfer to test@example.com** and id **[tr_00000000000000](https://dashboard.stripe.com/transfers/tr_00000000000000)** for amount **11.00aud** has failed." + + # use fixture named stripe_transfer_failed + self.send_and_test_stream_message('transfer_failed', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def test_transfer_paid(self): + # type: () -> None + expected_subject = u"stripe" + expected_message = u"The transfer with description **Transfer to test@example.com** and id **[tr_00000000000000](https://dashboard.stripe.com/transfers/tr_00000000000000)** for amount **11.00aud** has been paid." + + # use fixture named stripe_transfer_paid + self.send_and_test_stream_message('transfer_paid', expected_subject, expected_message, + content_type="application/x-www-form-urlencoded") + + def get_body(self, fixture_name): + # type: (text_type) -> text_type + return self.fixture_data("stripe", fixture_name, file_type="json") diff --git a/zerver/views/webhooks/stripe.py b/zerver/views/webhooks/stripe.py new file mode 100644 index 0000000000..35ae4c7f20 --- /dev/null +++ b/zerver/views/webhooks/stripe.py @@ -0,0 +1,126 @@ +# Webhooks for external integrations. +from __future__ import absolute_import +from django.utils.translation import ugettext as _ +from zerver.lib.actions import check_send_message +from zerver.lib.response import json_success, json_error +from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view +from zerver.lib.validator import check_dict, check_string +from zerver.models import Client, UserProfile + +from django.http import HttpRequest, HttpResponse +from six import text_type +from typing import Dict, Any, Iterable, Optional + +from datetime import datetime + +@api_key_only_webhook_view('Stripe') +@has_request_variables +def api_stripe_webhook(request, user_profile, client, + payload=REQ(argument_type='body'), stream=REQ(default='test'), + topic=REQ(default='stripe')): + # type: (HttpRequest, UserProfile, Client, Dict[str, Any], text_type, Optional[text_type]) -> HttpResponse + body = "" + event_type = "" + try: + event_type = payload["type"] + if event_type == "charge.dispute.closed": + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + link = "https://dashboard.stripe.com/payments/"+payload["data"]["object"]["charge"] + body_template = "A charge dispute for **" + amount_string + "** has been closed as **{object[status]}**.\n"\ + + "The charge in dispute was **[{object[charge]}](" + link + ")**." + body = body_template.format(**(payload["data"])) + elif event_type == "charge.dispute.created": + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + link = "https://dashboard.stripe.com/payments/"+payload["data"]["object"]["charge"] + body_template = "A charge dispute for **" + amount_string + "** has been created.\n"\ + + "The charge in dispute is **[{object[charge]}](" + link + ")**." + body = body_template.format(**(payload["data"])) + elif event_type == "charge.failed": + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + link = "https://dashboard.stripe.com/payments/"+payload["data"]["object"]["id"] + body_template = "A charge with id **[{object[id]}](" + link + ")** for **" + amount_string + "** has failed." + body = body_template.format(**(payload["data"])) + elif event_type == "charge.succeeded": + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + link = "https://dashboard.stripe.com/payments/"+payload["data"]["object"]["id"] + body_template = "A charge with id **[{object[id]}](" + link + ")** for **" + amount_string + "** has succeeded." + body = body_template.format(**(payload["data"])) + elif event_type == "customer.created": + link = "https://dashboard.stripe.com/customers/"+payload["data"]["object"]["id"] + if payload["data"]["object"]["email"] is None: + body_template = "A new customer with id **[{object[id]}](" + link + ")** has been created." + body = body_template.format(**(payload["data"])) + else: + body_template = "A new customer with id **[{object[id]}](" + link + ")** and email **{object[email]}** has been created." + body = body_template.format(**(payload["data"])) + elif event_type == "customer.deleted": + link = "https://dashboard.stripe.com/customers/"+payload["data"]["object"]["id"] + body_template = "A customer with id **[{object[id]}](" + link + ")** has been deleted." + body = body_template.format(**(payload["data"])) + elif event_type == "customer.subscription.created": + amount_string = amount(payload["data"]["object"]["plan"]["amount"], payload["data"]["object"]["plan"]["currency"]) + link = "https://dashboard.stripe.com/subscriptions/"+payload["data"]["object"]["id"] + body_template = "A new customer subscription for **" + amount_string + "** every **{plan[interval]}** has been created.\n" + body_template += "The subscription has id **[{id}](" + link + ")**." + body = body_template.format(**(payload["data"]["object"])) + elif event_type == "customer.subscription.deleted": + link = "https://dashboard.stripe.com/subscriptions/"+payload["data"]["object"]["id"] + body_template = "The customer subscription with id **[{object[id]}](" + link + ")** was deleted." + body = body_template.format(**(payload["data"])) + elif event_type == "customer.subscription.trial_will_end": + link = "https://dashboard.stripe.com/subscriptions/"+payload["data"]["object"]["id"] + body_template = "The customer subscription trial with id **[{object[id]}](" + link + ")** will end on " + body_template += datetime.fromtimestamp(payload["data"]["object"]["trial_end"]).strftime('%b %d %Y at %I:%M%p') + body = body_template.format(**(payload["data"])) + elif event_type == "invoice.payment_failed": + link = "https://dashboard.stripe.com/invoices/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount_due"], payload["data"]["object"]["currency"]) + body_template = "An invoice payment on invoice with id **[{object[id]}](" + link + ")** and with **"\ + + amount_string + "** due has failed." + body = body_template.format(**(payload["data"])) + elif event_type == "order.payment_failed": + link = "https://dashboard.stripe.com/orders/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + body_template = "An order payment on order with id **[{object[id]}](" + link + ")** for **" + amount_string + "** has failed." + body = body_template.format(**(payload["data"])) + elif event_type == "order.payment_succeeded": + link = "https://dashboard.stripe.com/orders/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + body_template = "An order payment on order with id **[{object[id]}](" + link + ")** for **"\ + + amount_string + "** has succeeded." + body = body_template.format(**(payload["data"])) + elif event_type == "order.updated": + link = "https://dashboard.stripe.com/orders/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + body_template = "The order with id **[{object[id]}](" + link + ")** for **" + amount_string + "** has been updated." + body = body_template.format(**(payload["data"])) + elif event_type == "transfer.failed": + link = "https://dashboard.stripe.com/transfers/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + body_template = "The transfer with description **{object[description]}** and id **[{object[id]}]("\ + + link + ")** for amount **"\ + + amount_string + "** has failed." + body = body_template.format(**(payload["data"])) + elif event_type == "transfer.paid": + link = "https://dashboard.stripe.com/transfers/"+payload["data"]["object"]["id"] + amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) + body_template = "The transfer with description **{object[description]}** and id **[{object[id]}]("\ + + link + ")** for amount **"\ + + amount_string + "** has been paid." + body = body_template.format(**(payload["data"])) + except KeyError as e: + body = "Missing key {} in JSON".format(str(e)) + + # send the message + check_send_message(user_profile, client, 'stream', [stream], topic, body) + + return json_success() + +def amount(amount, currency): + # type: (int, str) -> str + # zero-decimal currencies + zero_decimal_currencies = ["bif", "djf", "jpy", "krw", "pyg", "vnd", "xaf", "xpf", "clp", "gnf", "kmf", "mga", "rwf", "vuv", "xof"] + if currency in zero_decimal_currencies: + return str(amount) + currency + else: + return '{0:.02f}'.format(float(amount) * 0.01) + currency