snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
0
zerver/webhooks/stripe/__init__.py
Normal file
0
zerver/webhooks/stripe/__init__.py
Normal file
44
zerver/webhooks/stripe/doc.md
Normal file
44
zerver/webhooks/stripe/doc.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Zulip Stripe integration
|
||||
|
||||
Get Zulip notifications for Stripe events!
|
||||
|
||||
{% if 'http:' in external_url_scheme %}
|
||||
|
||||
!!! tip ""
|
||||
|
||||
Stripe will only send webhook payloads over HTTPS, but your Zulip
|
||||
server is configured to only accept HTTP. To use the Stripe
|
||||
webhook with this Zulip server, you will need to use a tunneling
|
||||
service like ngrok or Ultrahook.
|
||||
|
||||
{% endif %}
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
1. {!generate-webhook-url-basic.md!}
|
||||
|
||||
1. On your Stripe Dashboard, select **Developers** on the left
|
||||
sidebar. Select **Webhooks**, and click on **+ Add endpoint**.
|
||||
|
||||
1. Set the **URL to be called** to the URL generated above. Select the
|
||||
[event types](#filtering-incoming-events) you would like to be notified
|
||||
about, and click **Add endpoint**.
|
||||
|
||||
1. [Add a new linkifier](/help/add-a-custom-linkifier) in your Zulip
|
||||
organization. Set the pattern to `(?P<id>cus_[0-9a-zA-Z]+)` and the URL
|
||||
template to `https://dashboard.stripe.com/customers/{id}`. This step
|
||||
creates links to Stripe customers in Zulip messages and topics.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
{!event-filtering-additional-feature.md!}
|
||||
|
||||
### Related documentation
|
||||
|
||||
{!webhooks-url-specification.md!}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "account.updated",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2018-02-28",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "acct_00000000000000",
|
||||
"object": "account",
|
||||
"business_logo": null,
|
||||
"business_logo_large": null,
|
||||
"business_name": null,
|
||||
"business_primary_color": null,
|
||||
"business_url": null,
|
||||
"charges_enabled": false,
|
||||
"country": "CA",
|
||||
"created": 1523744779,
|
||||
"debit_negative_balances": true,
|
||||
"decline_charge_on": {
|
||||
"avs_failure": false,
|
||||
"cvc_failure": true
|
||||
},
|
||||
"default_currency": "cad",
|
||||
"details_submitted": true,
|
||||
"display_name": null,
|
||||
"email": "test@stripe.com",
|
||||
"external_accounts": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/accounts/acct_1CGwp5HSaWXyvFpK/external_accounts"
|
||||
},
|
||||
"legal_entity": {
|
||||
"additional_owners": [
|
||||
],
|
||||
"address": {
|
||||
"city": null,
|
||||
"country": "CA",
|
||||
"line1": null,
|
||||
"line2": null,
|
||||
"postal_code": null,
|
||||
"state": null
|
||||
},
|
||||
"business_name": null,
|
||||
"business_tax_id_provided": false,
|
||||
"dob": {
|
||||
"day": null,
|
||||
"month": null,
|
||||
"year": null
|
||||
},
|
||||
"first_name": null,
|
||||
"last_name": null,
|
||||
"personal_address": {
|
||||
"city": null,
|
||||
"country": "CA",
|
||||
"line1": null,
|
||||
"line2": null,
|
||||
"postal_code": null,
|
||||
"state": null
|
||||
},
|
||||
"personal_id_number_provided": false,
|
||||
"type": null,
|
||||
"verification": {
|
||||
"details": null,
|
||||
"details_code": null,
|
||||
"document": null,
|
||||
"document_back": null,
|
||||
"status": "unverified"
|
||||
}
|
||||
},
|
||||
"mcc": null,
|
||||
"metadata": {
|
||||
},
|
||||
"payout_schedule": {
|
||||
"delay_days": 7,
|
||||
"interval": "daily"
|
||||
},
|
||||
"payout_statement_descriptor": null,
|
||||
"payouts_enabled": false,
|
||||
"product_description": null,
|
||||
"statement_descriptor": "TEST",
|
||||
"support_address": null,
|
||||
"support_email": null,
|
||||
"support_phone": null,
|
||||
"support_url": null,
|
||||
"timezone": "America/St_Johns",
|
||||
"tos_acceptance": {
|
||||
"date": null,
|
||||
"ip": null,
|
||||
"user_agent": null
|
||||
},
|
||||
"type": "standard",
|
||||
"verification": {
|
||||
"disabled_reason": "fields_needed",
|
||||
"due_by": 1554669463,
|
||||
"fields_needed": [
|
||||
"legal_entity.verification.document"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
zerver/webhooks/stripe/fixtures/charge_dispute_closed.json
Normal file
61
zerver/webhooks/stripe/fixtures/charge_dispute_closed.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "charge.dispute.closed",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "dp_00000000000000",
|
||||
"object": "dispute",
|
||||
"amount": 1001,
|
||||
"balance_transactions": [],
|
||||
"charge": "ch_00000000000000",
|
||||
"created": 1480672052,
|
||||
"currency": "aud",
|
||||
"evidence": {
|
||||
"access_activity_log": null,
|
||||
"billing_address": null,
|
||||
"cancellation_policy": null,
|
||||
"cancellation_policy_disclosure": null,
|
||||
"cancellation_rebuttal": null,
|
||||
"customer_communication": null,
|
||||
"customer_email_address": null,
|
||||
"customer_name": null,
|
||||
"customer_purchase_ip": null,
|
||||
"customer_signature": null,
|
||||
"duplicate_charge_documentation": null,
|
||||
"duplicate_charge_explanation": null,
|
||||
"duplicate_charge_id": null,
|
||||
"product_description": null,
|
||||
"receipt": null,
|
||||
"refund_policy": null,
|
||||
"refund_policy_disclosure": null,
|
||||
"refund_refusal_explanation": null,
|
||||
"service_date": null,
|
||||
"service_documentation": null,
|
||||
"shipping_address": null,
|
||||
"shipping_carrier": null,
|
||||
"shipping_date": null,
|
||||
"shipping_documentation": null,
|
||||
"shipping_tracking_number": null,
|
||||
"uncategorized_file": null,
|
||||
"uncategorized_text": "Here is some evidence"
|
||||
},
|
||||
"evidence_details": {
|
||||
"due_by": 1482364799,
|
||||
"has_evidence": false,
|
||||
"past_due": false,
|
||||
"submission_count": 0
|
||||
},
|
||||
"is_charge_refundable": false,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"reason": "general",
|
||||
"status": "won"
|
||||
}
|
||||
}
|
||||
}
|
||||
61
zerver/webhooks/stripe/fixtures/charge_dispute_created.json
Normal file
61
zerver/webhooks/stripe/fixtures/charge_dispute_created.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "charge.dispute.created",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "dp_00000000000000",
|
||||
"object": "dispute",
|
||||
"amount": 1000,
|
||||
"balance_transactions": [],
|
||||
"charge": "ch_00000000000000",
|
||||
"created": 1480672043,
|
||||
"currency": "jpy",
|
||||
"evidence": {
|
||||
"access_activity_log": null,
|
||||
"billing_address": null,
|
||||
"cancellation_policy": null,
|
||||
"cancellation_policy_disclosure": null,
|
||||
"cancellation_rebuttal": null,
|
||||
"customer_communication": null,
|
||||
"customer_email_address": null,
|
||||
"customer_name": null,
|
||||
"customer_purchase_ip": null,
|
||||
"customer_signature": null,
|
||||
"duplicate_charge_documentation": null,
|
||||
"duplicate_charge_explanation": null,
|
||||
"duplicate_charge_id": null,
|
||||
"product_description": null,
|
||||
"receipt": null,
|
||||
"refund_policy": null,
|
||||
"refund_policy_disclosure": null,
|
||||
"refund_refusal_explanation": null,
|
||||
"service_date": null,
|
||||
"service_documentation": null,
|
||||
"shipping_address": null,
|
||||
"shipping_carrier": null,
|
||||
"shipping_date": null,
|
||||
"shipping_documentation": null,
|
||||
"shipping_tracking_number": null,
|
||||
"uncategorized_file": null,
|
||||
"uncategorized_text": null
|
||||
},
|
||||
"evidence_details": {
|
||||
"due_by": 1482364799,
|
||||
"has_evidence": false,
|
||||
"past_due": false,
|
||||
"submission_count": 0
|
||||
},
|
||||
"is_charge_refundable": false,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"reason": "general",
|
||||
"status": "needs_response"
|
||||
}
|
||||
}
|
||||
}
|
||||
76
zerver/webhooks/stripe/fixtures/charge_failed.json
Normal file
76
zerver/webhooks/stripe/fixtures/charge_failed.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "charge.failed",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "ch_00000000000000",
|
||||
"object": "charge",
|
||||
"amount": 100,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"balance_transaction": "txn_00000000000000",
|
||||
"captured": true,
|
||||
"created": 1480671978,
|
||||
"currency": "aud",
|
||||
"customer": null,
|
||||
"description": "My First Test Charge (created for API docs)",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"order": null,
|
||||
"outcome": null,
|
||||
"paid": false,
|
||||
"receipt_email": null,
|
||||
"receipt_number": null,
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_19MDcUCV4wXizEw4AsxeFwTn/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"id": "card_00000000000000",
|
||||
"object": "card",
|
||||
"address_city": null,
|
||||
"address_country": null,
|
||||
"address_line1": null,
|
||||
"address_line1_check": null,
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": null,
|
||||
"address_zip_check": null,
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": null,
|
||||
"cvc_check": null,
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 8,
|
||||
"exp_year": 2017,
|
||||
"funding": "credit",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": null,
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": null,
|
||||
"status": "succeeded"
|
||||
}
|
||||
}
|
||||
}
|
||||
89
zerver/webhooks/stripe/fixtures/charge_succeeded__card.json
Normal file
89
zerver/webhooks/stripe/fixtures/charge_succeeded__card.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"id": "evt_1DbqCuDEQaroqDjsojJSNhmh",
|
||||
"pending_webhooks": 1,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "ch_000000000000000000000000",
|
||||
"object": "charge",
|
||||
"amount": 100,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"balance_transaction": "txn_000000000000000000000000",
|
||||
"captured": true,
|
||||
"created": 1542991183,
|
||||
"currency": "aud",
|
||||
"customer": "cus_00000000000000",
|
||||
"description": null,
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {
|
||||
},
|
||||
"invoice": "in_000000000000000000000000",
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "user@example.com",
|
||||
"receipt_number": "0000-0000",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_000000000000000000000000/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"id": "card_000000000000000000000000",
|
||||
"object": "card",
|
||||
"address_city": "city",
|
||||
"address_country": "country",
|
||||
"address_line1": "line1",
|
||||
"address_line1_check": "unavailable",
|
||||
"address_line2": null,
|
||||
"address_state": "ST",
|
||||
"address_zip": "00000",
|
||||
"address_zip_check": "unavailable",
|
||||
"brand": "Visa",
|
||||
"country": "CO",
|
||||
"customer": "cus_00000000000000",
|
||||
"cvc_check": null,
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 1,
|
||||
"exp_year": 2023,
|
||||
"fingerprint": "0000000000000000",
|
||||
"funding": "credit",
|
||||
"last4": "0000",
|
||||
"metadata": {
|
||||
},
|
||||
"name": "name",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Cloud Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_group": null
|
||||
}
|
||||
},
|
||||
"type": "charge.succeeded",
|
||||
"api_version": "2018-11-08",
|
||||
"object": "event",
|
||||
"livemode": true,
|
||||
"created": 1543500572
|
||||
}
|
||||
117
zerver/webhooks/stripe/fixtures/charge_succeeded__invoice.json
Normal file
117
zerver/webhooks/stripe/fixtures/charge_succeeded__invoice.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"id": "evt_1DbqCuDEQaroqDjsojJSNhmh",
|
||||
"pending_webhooks": 1,
|
||||
"data": {
|
||||
"object": {
|
||||
"failure_message": null,
|
||||
"id": "py_000000000000000000000000",
|
||||
"amount": 100,
|
||||
"refunds": {
|
||||
"total_count": 0,
|
||||
"has_more": false,
|
||||
"url": "\/v1\/charges\/py_000000000000000000000000\/refunds",
|
||||
"object": "list",
|
||||
"data": [
|
||||
|
||||
]
|
||||
},
|
||||
"invoice": "in_000000000000000000000000",
|
||||
"application": null,
|
||||
"source": {
|
||||
"id": "src_000000000000000000000000",
|
||||
"type": "ach_credit_transfer",
|
||||
"usage": "reusable",
|
||||
"amount": null,
|
||||
"flow": "receiver",
|
||||
"customer": "cus_00000000000000",
|
||||
"livemode": true,
|
||||
"object": "source",
|
||||
"owner": {
|
||||
"address": null,
|
||||
"verified_name": null,
|
||||
"email": "username@example.com",
|
||||
"verified_phone": null,
|
||||
"phone": null,
|
||||
"verified_email": null,
|
||||
"verified_address": null,
|
||||
"name": null
|
||||
},
|
||||
"statement_descriptor": null,
|
||||
"metadata": {
|
||||
|
||||
},
|
||||
"client_secret": "secret",
|
||||
"currency": "usd",
|
||||
"receiver": {
|
||||
"amount_received": 100,
|
||||
"refund_attributes_status": "missing",
|
||||
"amount_charged": 100,
|
||||
"amount_returned": 0,
|
||||
"refund_attributes_method": "email",
|
||||
"address": "1234-1234"
|
||||
},
|
||||
"status": "pending",
|
||||
"ach_credit_transfer": {
|
||||
"refund_account_holder_name": null,
|
||||
"bank_name": "WELLS FARGO BANK, N.A.",
|
||||
"fingerprint": "1234",
|
||||
"routing_number": "1234",
|
||||
"swift_code": "WFBIUS6S",
|
||||
"refund_account_holder_type": null,
|
||||
"account_number": "1234",
|
||||
"refund_account_number": null,
|
||||
"refund_routing_number": null
|
||||
},
|
||||
"created": 1543500572
|
||||
},
|
||||
"refunded": false,
|
||||
"statement_descriptor": null,
|
||||
"metadata": {
|
||||
|
||||
},
|
||||
"transfer_group": null,
|
||||
"amount_refunded": 0,
|
||||
"currency": "usd",
|
||||
"application_fee": null,
|
||||
"source_transfer": null,
|
||||
"object": "charge",
|
||||
"receipt_email": "username@example.com",
|
||||
"receipt_number": "1234-1234",
|
||||
"captured": true,
|
||||
"failure_code": null,
|
||||
"description": null,
|
||||
"payment_intent": null,
|
||||
"paid": true,
|
||||
"shipping": null,
|
||||
"livemode": true,
|
||||
"balance_transaction": "txn_000000000000000000000000",
|
||||
"outcome": {
|
||||
"type": "authorized",
|
||||
"network_status": "approved_by_network",
|
||||
"risk_level": "not_assessed",
|
||||
"seller_message": "Payment complete.",
|
||||
"reason": null
|
||||
},
|
||||
"dispute": null,
|
||||
"on_behalf_of": null,
|
||||
"status": "succeeded",
|
||||
"fraud_details": {
|
||||
|
||||
},
|
||||
"order": null,
|
||||
"review": null,
|
||||
"customer": "cus_00000000000000",
|
||||
"destination": null,
|
||||
"created": 1543500571
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"id": null,
|
||||
"idempotency_key": "in_000000000000000000000000"
|
||||
},
|
||||
"type": "charge.succeeded",
|
||||
"api_version": "2018-11-08",
|
||||
"object": "event",
|
||||
"livemode": true,
|
||||
"created": 1543500572
|
||||
}
|
||||
41
zerver/webhooks/stripe/fixtures/customer_created.json
Normal file
41
zerver/webhooks/stripe/fixtures/customer_created.json
Normal file
@@ -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": null,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
zerver/webhooks/stripe/fixtures/customer_created_email.json
Normal file
41
zerver/webhooks/stripe/fixtures/customer_created_email.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
zerver/webhooks/stripe/fixtures/customer_deleted.json
Normal file
41
zerver/webhooks/stripe/fixtures/customer_deleted.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "customer.deleted",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "cus_00000000000000",
|
||||
"object": "customer",
|
||||
"account_balance": 0,
|
||||
"created": 1480672097,
|
||||
"currency": "aud",
|
||||
"default_source": null,
|
||||
"delinquent": false,
|
||||
"description": null,
|
||||
"discount": null,
|
||||
"email": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_9fYlWRryEw2I9B/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_9fYlWRryEw2I9B/subscriptions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"created":1326853478,
|
||||
"livemode":false,
|
||||
"id":"customer.discount.created_00000000000000",
|
||||
"type":"customer.discount.created",
|
||||
"object":"event",
|
||||
"request":null,
|
||||
"pending_webhooks":1,
|
||||
"api_version":"2018-02-28",
|
||||
"data":{
|
||||
"object":{
|
||||
"object":"discount",
|
||||
"coupon":{
|
||||
"id":"25_00000000000000",
|
||||
"object":"coupon",
|
||||
"amount_off":null,
|
||||
"created":1543438043,
|
||||
"currency":null,
|
||||
"duration":"repeating",
|
||||
"duration_in_months":3,
|
||||
"livemode":false,
|
||||
"max_redemptions":null,
|
||||
"metadata":{
|
||||
|
||||
},
|
||||
"name":"25.5% off",
|
||||
"percent_off":26,
|
||||
"percent_off_precise":25.5,
|
||||
"redeem_by":null,
|
||||
"times_redeemed":0,
|
||||
"valid":true
|
||||
},
|
||||
"customer":"cus_00000000000000",
|
||||
"end":1551386843,
|
||||
"start":1543438043,
|
||||
"subscription":null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"id": "evt_1DeFYZDEQaroqDjsX811NXeq",
|
||||
"object": "event",
|
||||
"api_version": "2018-11-08",
|
||||
"created": 1544074671,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_E6STM5w5EX3K28",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"billing": "send_invoice",
|
||||
"billing_cycle_anchor": 1544074670,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1544074670,
|
||||
"current_period_end": 1575610670,
|
||||
"current_period_start": 1544074670,
|
||||
"customer": "cus_00000000000000",
|
||||
"days_until_due": 7,
|
||||
"default_source": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_E6STR3cU0bwWFG",
|
||||
"object": "subscription_item",
|
||||
"created": 1544074671,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "flatrate",
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"subscription": "sub_E6STM5w5EX3K28"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_E6STM5w5EX3K28"
|
||||
},
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "flatrate",
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"start": 1544074670,
|
||||
"status": "active",
|
||||
"tax_percent": null,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
},
|
||||
"livemode": true,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": "req_J85F0AkACDDTBU",
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "customer.subscription.created"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"id": "evt_1DeFYZDEQaroqDjsX811NXeq",
|
||||
"object": "event",
|
||||
"api_version": "2018-11-08",
|
||||
"created": 1544074671,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_E6STM5w5EX3K28",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"billing": "send_invoice",
|
||||
"billing_cycle_anchor": 1544074670,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1544074670,
|
||||
"current_period_end": 1575610670,
|
||||
"current_period_start": 1544074670,
|
||||
"customer": "cus_00000000000000",
|
||||
"days_until_due": 7,
|
||||
"default_source": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_E6STR3cU0bwWFG",
|
||||
"object": "subscription_item",
|
||||
"created": 1544074671,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": null,
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"subscription": "sub_E6STM5w5EX3K28"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_E6STM5w5EX3K28"
|
||||
},
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": null,
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"start": 1544074670,
|
||||
"status": "active",
|
||||
"tax_percent": null,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
},
|
||||
"livemode": true,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": "req_J85F0AkACDDTBU",
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "customer.subscription.created"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "customer.subscription.deleted",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_00000000000000",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1480672113,
|
||||
"current_period_end": 1483350513,
|
||||
"current_period_start": 1480672113,
|
||||
"customer": "cus_00000000000000",
|
||||
"discount": null,
|
||||
"ended_at": 1480633660,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"plan": {
|
||||
"id": "gold_00000000000000",
|
||||
"object": "plan",
|
||||
"amount": 2000,
|
||||
"created": 1394782886,
|
||||
"currency": "aud",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"name": "Gold Special",
|
||||
"statement_descriptor": null,
|
||||
"trial_period_days": null
|
||||
},
|
||||
"quantity": 1,
|
||||
"start": 1480672113,
|
||||
"status": "canceled",
|
||||
"tax_percent": null,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "customer.subscription.trial_will_end",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_00000000000000",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1480672121,
|
||||
"current_period_end": 1483350521,
|
||||
"current_period_start": 1480672121,
|
||||
"customer": "cus_00000000000000",
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"plan": {
|
||||
"id": "gold_00000000000000",
|
||||
"object": "plan",
|
||||
"amount": 2000,
|
||||
"created": 1394782886,
|
||||
"currency": "aud",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"name": "Gold Special",
|
||||
"statement_descriptor": null,
|
||||
"trial_period_days": null
|
||||
},
|
||||
"quantity": 1,
|
||||
"start": 1480672121,
|
||||
"status": "trialing",
|
||||
"tax_percent": null,
|
||||
"trial_end": 1480892861,
|
||||
"trial_start": 1480633661
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"id": "evt_1DeFo8DEQaroqDjsk4tzzzdV",
|
||||
"object": "event",
|
||||
"api_version": "2018-11-08",
|
||||
"created": 1544075636,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_E6STM5w5EX3K28",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"billing": "send_invoice",
|
||||
"billing_cycle_anchor": 1572609600,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1544074670,
|
||||
"current_period_end": 1572609600,
|
||||
"current_period_start": 1544075635,
|
||||
"customer": "cus_00000000000000",
|
||||
"days_until_due": 7,
|
||||
"default_source": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_E6STR3cU0bwWFG",
|
||||
"object": "subscription_item",
|
||||
"created": 1544074671,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "flatrate",
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"subscription": "sub_E6STM5w5EX3K28"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_E6STM5w5EX3K28"
|
||||
},
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_E6SQ6RAtmLVtzg",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 100,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1544074513,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": true,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "flatrate",
|
||||
"product": "prod_E6SPZ5RKfKClNL",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 800,
|
||||
"start": 1544075635,
|
||||
"status": "trialing",
|
||||
"tax_percent": null,
|
||||
"trial_end": 1572609600,
|
||||
"trial_start": 1544075635
|
||||
},
|
||||
"previous_attributes": {
|
||||
"billing_cycle_anchor": 1544074670,
|
||||
"current_period_end": 1575610670,
|
||||
"current_period_start": 1544074670,
|
||||
"start": 1544074670,
|
||||
"status": "active",
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
},
|
||||
"livemode": true,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": "req_X3p7qbGSX2wZ8D",
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "customer.subscription.updated"
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"id": "evt_1DbqCuDEQaroqDjsIkZIT6uP",
|
||||
"object": "event",
|
||||
"api_version": "2018-11-08",
|
||||
"created": 1543500572,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "cus_00000000000000",
|
||||
"object": "customer",
|
||||
"account_balance": 100,
|
||||
"created": 1543971209,
|
||||
"currency": "usd",
|
||||
"default_source": "src_1DdoduGh0CmXqmnwKJmq8lpv",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"invoice_prefix": "6E2831C",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "2",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "src_1DdoduGh0CmXqmnwKJmq8lpv",
|
||||
"object": "source",
|
||||
"ach_credit_transfer": {
|
||||
"account_number": "test_a479e261d1bb",
|
||||
"bank_name": "TEST BANK",
|
||||
"fingerprint": "cAzkzdbKgZYS8NhC",
|
||||
"routing_number": "110000000",
|
||||
"swift_code": "TSTEZ122",
|
||||
"refund_routing_number": null,
|
||||
"refund_account_number": null,
|
||||
"refund_account_holder_type": null,
|
||||
"refund_account_holder_name": null
|
||||
},
|
||||
"amount": null,
|
||||
"client_secret": "src_client_secret_E60fLRPcrzhAJNmOjvsUHTli",
|
||||
"created": 1543971214,
|
||||
"currency": "usd",
|
||||
"customer": "cus_E60fF1CuG8K36D",
|
||||
"flow": "receiver",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"owner": {
|
||||
"address": null,
|
||||
"email": "amount_0@stripe.com",
|
||||
"name": null,
|
||||
"phone": null,
|
||||
"verified_address": null,
|
||||
"verified_email": null,
|
||||
"verified_name": null,
|
||||
"verified_phone": null
|
||||
},
|
||||
"receiver": {
|
||||
"address": "110000000-test_a479e261d1bb",
|
||||
"amount_charged": 0,
|
||||
"amount_received": 0,
|
||||
"amount_returned": 0,
|
||||
"refund_attributes_method": "email",
|
||||
"refund_attributes_status": "missing"
|
||||
},
|
||||
"statement_descriptor": null,
|
||||
"status": "pending",
|
||||
"type": "ach_credit_transfer",
|
||||
"usage": "reusable"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_E60fF1CuG8K36D/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "sub_E60fxv28JMUWOG",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": null,
|
||||
"billing": "send_invoice",
|
||||
"billing_cycle_anchor": 1543971209,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1543971209,
|
||||
"current_period_end": 1575507209,
|
||||
"current_period_start": 1543971209,
|
||||
"customer": "cus_E60fF1CuG8K36D",
|
||||
"days_until_due": 30,
|
||||
"default_source": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_E60fDuBL4e55ZF",
|
||||
"object": "subscription_item",
|
||||
"created": 1543971210,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "annual",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_E60fxv28JMUWOG"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_E60fxv28JMUWOG"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"plan": {
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"nickname": "annual",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1543971210,
|
||||
"status": "active",
|
||||
"tax_percent": 0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_E60fF1CuG8K36D/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
},
|
||||
"previous_attributes": {
|
||||
"account_balance": 0
|
||||
}
|
||||
},
|
||||
"livemode": true,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": null,
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "customer.updated"
|
||||
}
|
||||
140
zerver/webhooks/stripe/fixtures/invoice_created.json
Normal file
140
zerver/webhooks/stripe/fixtures/invoice_created.json
Normal file
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"id": "evt_1GpmuvHLwdCOCoR7Q22hCa2N",
|
||||
"object": "event",
|
||||
"api_version": "2020-03-02",
|
||||
"created": 1591153524,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "in_1GpmuuHLwdCOCoR7ghzQDQLW",
|
||||
"object": "invoice",
|
||||
"account_country": "IN",
|
||||
"account_name": null,
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee_amount": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": false,
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"collection_method": "send_invoice",
|
||||
"created": 1591153524,
|
||||
"currency": "inr",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_HH97asvHvaYQYp",
|
||||
"customer_address": null,
|
||||
"customer_email": "zenitsu_agatsuma@mail.example.com",
|
||||
"customer_name": "Zenitsu Agatsuma",
|
||||
"customer_phone": null,
|
||||
"customer_shipping": {
|
||||
"address": {
|
||||
"city": "",
|
||||
"country": "",
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"postal_code": "",
|
||||
"state": ""
|
||||
},
|
||||
"name": "Zenitsu Agatsuma",
|
||||
"phone": ""
|
||||
},
|
||||
"customer_tax_exempt": "none",
|
||||
"customer_tax_ids": [],
|
||||
"default_payment_method": null,
|
||||
"default_source": null,
|
||||
"default_tax_rates": [],
|
||||
"description": null,
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "il_1GpmuuHLwdCOCoR7nCRM5MXl",
|
||||
"object": "line_item",
|
||||
"amount": 0,
|
||||
"currency": "inr",
|
||||
"description": null,
|
||||
"discountable": true,
|
||||
"invoice_item": "ii_1GpmuuHLwdCOCoR7VOeTr2BM",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"period": {
|
||||
"end": 1591153524,
|
||||
"start": 1591153524
|
||||
},
|
||||
"plan": null,
|
||||
"price": {
|
||||
"id": "price_1GpmuuHLwdCOCoR7ff5A7n1i",
|
||||
"object": "price",
|
||||
"active": false,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1591153524,
|
||||
"currency": "inr",
|
||||
"livemode": false,
|
||||
"lookup_key": null,
|
||||
"metadata": {},
|
||||
"nickname": null,
|
||||
"product": "prod_HOa5MrXjZ9YBuA",
|
||||
"recurring": null,
|
||||
"tiers_mode": null,
|
||||
"transform_quantity": null,
|
||||
"type": "one_time",
|
||||
"unit_amount": 0,
|
||||
"unit_amount_decimal": "0"
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"tax_amounts": [],
|
||||
"tax_rates": [],
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_1GpmuuHLwdCOCoR7ghzQDQLW/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": null,
|
||||
"paid": false,
|
||||
"payment_intent": null,
|
||||
"period_end": 1591153524,
|
||||
"period_start": 1591153524,
|
||||
"post_payment_credit_notes_amount": 0,
|
||||
"pre_payment_credit_notes_amount": 0,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": null,
|
||||
"status": "draft",
|
||||
"status_transitions": {
|
||||
"finalized_at": null,
|
||||
"marked_uncollectible_at": null,
|
||||
"paid_at": null,
|
||||
"voided_at": null
|
||||
},
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": null,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"total_tax_amounts": [],
|
||||
"transfer_data": null,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
},
|
||||
"livemode": false,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": "req_cz0vh1VJ1U4MzE",
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "invoice.created"
|
||||
}
|
||||
83
zerver/webhooks/stripe/fixtures/invoice_payment_failed.json
Normal file
83
zerver/webhooks/stripe/fixtures/invoice_payment_failed.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "evt_00000000000000",
|
||||
"type": "invoice.payment_failed",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2016-07-06",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "in_00000000000000",
|
||||
"object": "invoice",
|
||||
"amount_due": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"charge": null,
|
||||
"closed": false,
|
||||
"currency": "aud",
|
||||
"customer": "cus_00000000000000",
|
||||
"date": 1480672129,
|
||||
"description": null,
|
||||
"discount": null,
|
||||
"ending_balance": null,
|
||||
"forgiven": false,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"id": "sub_9fYmJQOQy04yoh",
|
||||
"object": "line_item",
|
||||
"amount": 2000,
|
||||
"currency": "aud",
|
||||
"description": null,
|
||||
"discountable": true,
|
||||
"livemode": true,
|
||||
"metadata": {},
|
||||
"period": {
|
||||
"start": 1483350529,
|
||||
"end": 1486028929
|
||||
},
|
||||
"plan": {
|
||||
"id": "gold",
|
||||
"object": "plan",
|
||||
"amount": 2000,
|
||||
"created": 1480672129,
|
||||
"currency": "aud",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"name": "Gold Special",
|
||||
"statement_descriptor": null,
|
||||
"trial_period_days": null
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "subscription"
|
||||
}
|
||||
],
|
||||
"total_count": 1,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices/in_19MDevCV4wXizEw4B4lY7YPR/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1480675729,
|
||||
"paid": false,
|
||||
"period_end": 1480672129,
|
||||
"period_start": 1480672129,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": null,
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": null,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
}
|
||||
}
|
||||
149
zerver/webhooks/stripe/fixtures/invoice_updated__open.json
Normal file
149
zerver/webhooks/stripe/fixtures/invoice_updated__open.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"object": "event",
|
||||
"type": "invoice.updated",
|
||||
"pending_webhooks": 1,
|
||||
"created": 1560063585,
|
||||
"livemode": false,
|
||||
"request": {
|
||||
"idempotency_key": null,
|
||||
"id": "req_wRT7KgbXvlQMfC"
|
||||
},
|
||||
"data": {
|
||||
"previous_attributes": {
|
||||
"status": "draft",
|
||||
"due_date": null,
|
||||
"invoice_pdf": null,
|
||||
"status_transitions": {
|
||||
"finalized_at": null
|
||||
},
|
||||
"number": "B7C8CA7D-DRAFT",
|
||||
"ending_balance": null,
|
||||
"payment_intent": null,
|
||||
"hosted_invoice_url": null
|
||||
},
|
||||
"object": {
|
||||
"default_tax_rates": [],
|
||||
"livemode": false,
|
||||
"tax": null,
|
||||
"number": "B7C8CA7D-0001",
|
||||
"attempt_count": 0,
|
||||
"currency": "usd",
|
||||
"payment_intent": "pi_1EjL0HHuGUuNWDDZ6TdrsWzQ",
|
||||
"default_payment_method": null,
|
||||
"total": 3000,
|
||||
"account_name": null,
|
||||
"custom_fields": [
|
||||
{
|
||||
"name": "Custom field",
|
||||
"value": "test value"
|
||||
}
|
||||
],
|
||||
"customer_name": "Test Customer",
|
||||
"customer_shipping": null,
|
||||
"customer_tax_ids": [],
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_444pftvnD8HFMJCXHn6TQKTDMc/pdf",
|
||||
"billing": "send_invoice",
|
||||
"footer": "Some random help and legal information.",
|
||||
"description": "Thank you for your business!",
|
||||
"customer_phone": null,
|
||||
"id": "in_1EjKuzHuGUuNWDDZmFUP3FxU",
|
||||
"webhooks_delivered_at": 1560063257,
|
||||
"charge": null,
|
||||
"period_end": 1560063257,
|
||||
"discount": null,
|
||||
"customer_tax_exempt": "none",
|
||||
"post_payment_credit_notes_amount": 0,
|
||||
"metadata": {},
|
||||
"status": "open",
|
||||
"due_date": 1562655586,
|
||||
"pre_payment_credit_notes_amount": 0,
|
||||
"customer_email": "test_customer@test.gmail.com",
|
||||
"collection_method": "send_invoice",
|
||||
"statement_descriptor": null,
|
||||
"attempted": false,
|
||||
"amount_paid": 0,
|
||||
"object": "invoice",
|
||||
"amount_remaining": 3000,
|
||||
"paid": false,
|
||||
"customer_address": null,
|
||||
"auto_advance": true,
|
||||
"ending_balance": 0,
|
||||
"billing_reason": "manual",
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_444pftvnD8HFMJCXHn6TQKTDMc",
|
||||
"period_start": 1560063257,
|
||||
"subtotal": 3000,
|
||||
"tax_percent": null,
|
||||
"subscription": null,
|
||||
"customer": "cus_FDmT2sIGjFskP2",
|
||||
"total_tax_amounts": [],
|
||||
"application_fee_amount": null,
|
||||
"next_payment_attempt": null,
|
||||
"created": 1560063257,
|
||||
"status_transitions": {
|
||||
"voided_at": null,
|
||||
"finalized_at": 1560063585,
|
||||
"marked_uncollectible_at": null,
|
||||
"paid_at": null
|
||||
},
|
||||
"default_source": null,
|
||||
"lines": {
|
||||
"has_more": false,
|
||||
"total_count": 2,
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"description": "Watermelons",
|
||||
"subscription": null,
|
||||
"proration": false,
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"start": 1560063400,
|
||||
"end": 1560063400
|
||||
},
|
||||
"tax_rates": [],
|
||||
"currency": "usd",
|
||||
"amount": 1000,
|
||||
"invoice_item": "ii_1EjKxIHuGUuNWDDZhiVHinLl",
|
||||
"tax_amounts": [],
|
||||
"plan": null,
|
||||
"quantity": 2,
|
||||
"type": "invoiceitem",
|
||||
"id": "ii_1EjKxIHuGUuNWDDZhiVHinLl",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"description": "Apples",
|
||||
"subscription": null,
|
||||
"proration": false,
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"start": 1560063257,
|
||||
"end": 1560063257
|
||||
},
|
||||
"tax_rates": [],
|
||||
"currency": "usd",
|
||||
"amount": 2000,
|
||||
"invoice_item": "ii_1EjKuzHuGUuNWDDZKrPUb2KA",
|
||||
"tax_amounts": [],
|
||||
"plan": null,
|
||||
"quantity": 10,
|
||||
"type": "invoiceitem",
|
||||
"id": "ii_1EjKuzHuGUuNWDDZKrPUb2KA",
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"url": "/v1/invoices/in_1EjKuzHuGUuNWDDZmFUP3FxU/lines"
|
||||
},
|
||||
"account_country": "US",
|
||||
"starting_balance": 0,
|
||||
"amount_due": 3000,
|
||||
"receipt_number": null
|
||||
}
|
||||
},
|
||||
"id": "evt_1EjL0IHuGUuNWDDZCFLOYrNv",
|
||||
"api_version": "2019-03-14"
|
||||
}
|
||||
125
zerver/webhooks/stripe/fixtures/invoice_updated__paid.json
Normal file
125
zerver/webhooks/stripe/fixtures/invoice_updated__paid.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"object": "event",
|
||||
"type": "invoice.updated",
|
||||
"pending_webhooks": 1,
|
||||
"created": 1560064810,
|
||||
"livemode": false,
|
||||
"request": {
|
||||
"idempotency_key": null,
|
||||
"id": "req_4ITsSSm2tprAwz"
|
||||
},
|
||||
"data": {
|
||||
"previous_attributes": {
|
||||
"status": "draft",
|
||||
"due_date": null,
|
||||
"invoice_pdf": null,
|
||||
"attempted": false,
|
||||
"number": "113948B9-DRAFT",
|
||||
"ending_balance": null,
|
||||
"auto_advance": true,
|
||||
"paid": false,
|
||||
"status_transitions": {
|
||||
"finalized_at": null,
|
||||
"paid_at": null
|
||||
},
|
||||
"hosted_invoice_url": null
|
||||
},
|
||||
"object": {
|
||||
"default_tax_rates": [],
|
||||
"livemode": false,
|
||||
"tax": null,
|
||||
"number": "113948B9-0001",
|
||||
"attempt_count": 0,
|
||||
"currency": "usd",
|
||||
"payment_intent": null,
|
||||
"default_payment_method": null,
|
||||
"total": 100,
|
||||
"account_name": null,
|
||||
"custom_fields": null,
|
||||
"customer_name": "Hemanth V. Alluri",
|
||||
"customer_shipping": null,
|
||||
"customer_tax_ids": [],
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_iTAW0hYwxklggGxPhtXkSyUF8y/pdf",
|
||||
"billing": "send_invoice",
|
||||
"footer": null,
|
||||
"description": null,
|
||||
"customer_phone": null,
|
||||
"id": "in_1EjLINHuGUuNWDDZjDf2WNqd",
|
||||
"webhooks_delivered_at": 1560064708,
|
||||
"charge": null,
|
||||
"period_end": 1560064707,
|
||||
"discount": null,
|
||||
"customer_tax_exempt": "none",
|
||||
"post_payment_credit_notes_amount": 0,
|
||||
"metadata": {},
|
||||
"status": "paid",
|
||||
"due_date": 1560151211,
|
||||
"pre_payment_credit_notes_amount": 0,
|
||||
"customer_email": "hemanth.alluri.123@gmail.com",
|
||||
"collection_method": "send_invoice",
|
||||
"statement_descriptor": null,
|
||||
"attempted": true,
|
||||
"amount_paid": 100,
|
||||
"object": "invoice",
|
||||
"amount_remaining": 0,
|
||||
"paid": true,
|
||||
"customer_address": null,
|
||||
"auto_advance": false,
|
||||
"ending_balance": 0,
|
||||
"billing_reason": "manual",
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_iTAW0hYwxklggGxPhtXkSyUF8y",
|
||||
"period_start": 1560064707,
|
||||
"subtotal": 100,
|
||||
"tax_percent": null,
|
||||
"subscription": null,
|
||||
"customer": "cus_FDmrSwQt9Fck5M",
|
||||
"total_tax_amounts": [],
|
||||
"application_fee_amount": null,
|
||||
"next_payment_attempt": null,
|
||||
"created": 1560064707,
|
||||
"status_transitions": {
|
||||
"voided_at": null,
|
||||
"finalized_at": 1560064810,
|
||||
"marked_uncollectible_at": null,
|
||||
"paid_at": 1560064810
|
||||
},
|
||||
"default_source": null,
|
||||
"lines": {
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"description": "Pen",
|
||||
"subscription": null,
|
||||
"proration": false,
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"start": 1560064707,
|
||||
"end": 1560064707
|
||||
},
|
||||
"tax_rates": [],
|
||||
"currency": "usd",
|
||||
"amount": 100,
|
||||
"invoice_item": "ii_1EjLINHuGUuNWDDZJX6Nl48C",
|
||||
"tax_amounts": [],
|
||||
"plan": null,
|
||||
"quantity": 1,
|
||||
"type": "invoiceitem",
|
||||
"id": "ii_1EjLINHuGUuNWDDZJX6Nl48C",
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"url": "/v1/invoices/in_1EjLINHuGUuNWDDZjDf2WNqd/lines"
|
||||
},
|
||||
"account_country": "US",
|
||||
"starting_balance": 0,
|
||||
"amount_due": 100,
|
||||
"receipt_number": null
|
||||
}
|
||||
},
|
||||
"id": "evt_1EjLK2HuGUuNWDDZZ88XyWhO",
|
||||
"api_version": "2019-03-14"
|
||||
}
|
||||
35
zerver/webhooks/stripe/fixtures/invoiceitem_created.json
Normal file
35
zerver/webhooks/stripe/fixtures/invoiceitem_created.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"created": 1326853478,
|
||||
"livemode": false,
|
||||
"id": "invoiceitem.created_00000000000000",
|
||||
"type": "invoiceitem.created",
|
||||
"object": "event",
|
||||
"request": null,
|
||||
"pending_webhooks": 1,
|
||||
"api_version": "2018-02-28",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "ii_00000000000000",
|
||||
"object": "invoiceitem",
|
||||
"amount": 1000,
|
||||
"currency": "cad",
|
||||
"customer": "cus_00000000000000",
|
||||
"date": 1549404213,
|
||||
"description": "My First Invoice Item (created for API docs)",
|
||||
"discountable": true,
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"period": {
|
||||
"end": 1549404213,
|
||||
"start": 1549404213
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"object": "event",
|
||||
"type": "invoiceitem.updated",
|
||||
"pending_webhooks": 1,
|
||||
"created": 1560063405,
|
||||
"livemode": false,
|
||||
"request": {
|
||||
"idempotency_key": null,
|
||||
"id": "req_9TWblyRuNHDMEr"
|
||||
},
|
||||
"data": {
|
||||
"previous_attributes": {
|
||||
"description": null
|
||||
},
|
||||
"object": {
|
||||
"customer": "cus_FDmT2sIGjFskP2",
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"description": "Watermelons",
|
||||
"subscription": null,
|
||||
"proration": false,
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"start": 1560063400,
|
||||
"end": 1560063400
|
||||
},
|
||||
"tax_rates": [],
|
||||
"currency": "usd",
|
||||
"amount": 0,
|
||||
"plan": null,
|
||||
"invoice": "in_1EjKuzHuGUuNWDDZmFUP3FxU",
|
||||
"date": 1560063400,
|
||||
"unit_amount": 0,
|
||||
"quantity": 1,
|
||||
"id": "ii_1EjKxIHuGUuNWDDZhiVHinLl",
|
||||
"metadata": {}
|
||||
}
|
||||
},
|
||||
"id": "evt_1EjKxNHuGUuNWDDZXVzriO4I",
|
||||
"api_version": "2019-03-14"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"object": "event",
|
||||
"type": "invoiceitem.updated",
|
||||
"pending_webhooks": 1,
|
||||
"created": 1560063407,
|
||||
"livemode": false,
|
||||
"request": {
|
||||
"idempotency_key": null,
|
||||
"id": "req_AApRWrApSzs6rP"
|
||||
},
|
||||
"data": {
|
||||
"previous_attributes": {
|
||||
"quantity": 1
|
||||
},
|
||||
"object": {
|
||||
"customer": "cus_FDmT2sIGjFskP2",
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"description": "Watermelons",
|
||||
"subscription": null,
|
||||
"proration": false,
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"start": 1560063400,
|
||||
"end": 1560063400
|
||||
},
|
||||
"tax_rates": [],
|
||||
"currency": "usd",
|
||||
"amount": 0,
|
||||
"plan": null,
|
||||
"invoice": "in_1EjKuzHuGUuNWDDZmFUP3FxU",
|
||||
"date": 1560063400,
|
||||
"unit_amount": 0,
|
||||
"quantity": 0,
|
||||
"id": "ii_1EjKxIHuGUuNWDDZhiVHinLl",
|
||||
"metadata": {}
|
||||
}
|
||||
},
|
||||
"id": "evt_1EjKxPHuGUuNWDDZvbb9RsLf",
|
||||
"api_version": "2019-03-14"
|
||||
}
|
||||
32
zerver/webhooks/stripe/fixtures/pseudo_refund_event.json
Normal file
32
zerver/webhooks/stripe/fixtures/pseudo_refund_event.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "evt_abcde12345ABCDE",
|
||||
"object": "event",
|
||||
"api_version": "2018-05-21",
|
||||
"created": 1581391347,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "pyr_abcde12345ABCDF",
|
||||
"object": "refund",
|
||||
"amount": 1234,
|
||||
"balance_transaction": null,
|
||||
"charge": "py_abcde12345ABCDG",
|
||||
"created": 1589212391,
|
||||
"currency": "eur",
|
||||
"metadata": {},
|
||||
"payment_intent": "pi_abcd1234ABCDH",
|
||||
"reason": null,
|
||||
"receipt_number": null,
|
||||
"source_transfer_reversal": null,
|
||||
"status": "succeeded",
|
||||
"transfer_reversal": null
|
||||
},
|
||||
"previous_attributes": {}
|
||||
},
|
||||
"livemode": true,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": null,
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "charge.refund.updated"
|
||||
}
|
||||
38
zerver/webhooks/stripe/fixtures/refund_event.json
Normal file
38
zerver/webhooks/stripe/fixtures/refund_event.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "evt_1Gib6dHLwdCOCoR7mbMhSVu0",
|
||||
"object": "event",
|
||||
"api_version": "2020-03-02",
|
||||
"created": 1589439827,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "re_1Gib6ZHLwdCOCoR7VrzCnXlj",
|
||||
"object": "refund",
|
||||
"amount": 30000000,
|
||||
"balance_transaction": "txn_1Gib6ZHLwdCOCoR71Gd83huz",
|
||||
"charge": "ch_1Gib61HLwdCOCoR71rnkccye",
|
||||
"created": 1589439823,
|
||||
"currency": "inr",
|
||||
"failure_balance_transaction": "txn_1Gib6dHLwdCOCoR7VQXTaZVj",
|
||||
"failure_reason": "expired_or_canceled_card",
|
||||
"metadata": {},
|
||||
"payment_intent": "pi_1Gib60HLwdCOCoR7KJbTO3U7",
|
||||
"reason": null,
|
||||
"receipt_number": null,
|
||||
"source_transfer_reversal": null,
|
||||
"status": "failed",
|
||||
"transfer_reversal": null
|
||||
},
|
||||
"previous_attributes": {
|
||||
"failure_balance_transaction": null,
|
||||
"failure_reason": null,
|
||||
"status": "succeeded"
|
||||
}
|
||||
},
|
||||
"livemode": false,
|
||||
"pending_webhooks": 1,
|
||||
"request": {
|
||||
"id": null,
|
||||
"idempotency_key": null
|
||||
},
|
||||
"type": "charge.refund.updated"
|
||||
}
|
||||
268
zerver/webhooks/stripe/tests.py
Normal file
268
zerver/webhooks/stripe/tests.py
Normal file
@@ -0,0 +1,268 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import orjson
|
||||
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class StripeHookTests(WebhookTestCase):
|
||||
def test_charge_dispute_closed(self) -> None:
|
||||
expected_topic_name = "disputes"
|
||||
expected_message = "[Dispute](https://dashboard.stripe.com/disputes/dp_00000000000000) closed. Current status: won."
|
||||
self.check_webhook(
|
||||
"charge_dispute_closed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_charge_dispute_created(self) -> None:
|
||||
expected_topic_name = "disputes"
|
||||
expected_message = "[Dispute](https://dashboard.stripe.com/disputes/dp_00000000000000) created. Current status: needs response."
|
||||
self.check_webhook(
|
||||
"charge_dispute_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_charge_dispute_created_pdp_prefix(self) -> None:
|
||||
self.subscribe(self.test_user, self.channel_name)
|
||||
payload = orjson.loads(self.get_body("charge_dispute_created"))
|
||||
payload["data"]["object"]["id"] = "pdp_00000000000000"
|
||||
msg = self.send_webhook_payload(
|
||||
self.test_user,
|
||||
self.url,
|
||||
orjson.dumps(payload).decode(),
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
self.assert_channel_message(
|
||||
message=msg,
|
||||
channel_name=self.channel_name,
|
||||
topic_name="disputes",
|
||||
content="[Dispute](https://dashboard.stripe.com/disputes/pdp_00000000000000) created. Current status: needs response.",
|
||||
)
|
||||
|
||||
def test_charge_failed(self) -> None:
|
||||
expected_topic_name = "charges"
|
||||
expected_message = (
|
||||
"[Charge](https://dashboard.stripe.com/charges/ch_00000000000000) for 1.00 AUD failed"
|
||||
)
|
||||
self.check_webhook(
|
||||
"charge_failed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
# Credit card charge
|
||||
def test_charge_succeeded__card(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "[Charge](https://dashboard.stripe.com/charges/ch_000000000000000000000000) for 1.00 AUD succeeded"
|
||||
self.check_webhook(
|
||||
"charge_succeeded__card",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
# ACH payment (really a 'payment', rather than a 'charge')
|
||||
def test_charge_succeeded__invoice(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "[Payment](https://dashboard.stripe.com/payments/py_000000000000000000000000) for $1.00 succeeded"
|
||||
self.check_webhook(
|
||||
"charge_succeeded__invoice",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_created(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = (
|
||||
"[Customer](https://dashboard.stripe.com/customers/cus_00000000000000) created"
|
||||
)
|
||||
self.check_webhook(
|
||||
"customer_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_created_email(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "[Customer](https://dashboard.stripe.com/customers/cus_00000000000000) created\nEmail: example@abc.com"
|
||||
self.check_webhook(
|
||||
"customer_created_email",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_deleted(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = (
|
||||
"[Customer](https://dashboard.stripe.com/customers/cus_00000000000000) deleted"
|
||||
)
|
||||
self.check_webhook(
|
||||
"customer_deleted",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_subscription_created(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = """\
|
||||
[Subscription](https://dashboard.stripe.com/subscriptions/sub_E6STM5w5EX3K28) created
|
||||
Plan: [flatrate](https://dashboard.stripe.com/plans/plan_E6SQ6RAtmLVtzg)
|
||||
Quantity: 800
|
||||
Billing method: send invoice"""
|
||||
self.check_webhook(
|
||||
"customer_subscription_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_subscription_created_no_nickname(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = """\
|
||||
[Subscription](https://dashboard.stripe.com/subscriptions/sub_E6STM5w5EX3K28) created
|
||||
Plan: https://dashboard.stripe.com/plans/plan_E6SQ6RAtmLVtzg
|
||||
Quantity: 800
|
||||
Billing method: send invoice"""
|
||||
self.check_webhook(
|
||||
"customer_subscription_created_no_nickname",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_subscription_deleted(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = (
|
||||
"[Subscription](https://dashboard.stripe.com/subscriptions/sub_00000000000000) deleted"
|
||||
)
|
||||
self.check_webhook(
|
||||
"customer_subscription_deleted",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_subscription_updated(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = """\
|
||||
[Subscription](https://dashboard.stripe.com/subscriptions/sub_E6STM5w5EX3K28) updated
|
||||
* Billing cycle anchor is now <time:2019-11-01T12:00:00+00:00>
|
||||
* Current period end is now <time:2019-11-01T12:00:00+00:00>
|
||||
* Current period start is now <time:2018-12-06T05:53:55+00:00>
|
||||
* Start is now <time:2018-12-06T05:53:55+00:00>
|
||||
* Status is now trialing
|
||||
* Trial end is now <time:2019-11-01T12:00:00+00:00>
|
||||
* Trial start is now <time:2018-12-06T05:53:55+00:00>"""
|
||||
self.check_webhook(
|
||||
"customer_subscription_updated",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_subscription_trial_will_end(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "[Subscription](https://dashboard.stripe.com/subscriptions/sub_00000000000000) trial will end in 3 days"
|
||||
# 3 days before the end of the trial, plus a little bit to make sure the rounding is working
|
||||
with mock.patch("time.time", return_value=1480892861 - 3 * 3600 * 24 + 100):
|
||||
# use fixture named stripe_customer_subscription_trial_will_end
|
||||
self.check_webhook(
|
||||
"customer_subscription_trial_will_end",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_updated__account_balance(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = (
|
||||
"[Customer](https://dashboard.stripe.com/customers/cus_00000000000000) updated"
|
||||
"\n* Account balance is now 100"
|
||||
)
|
||||
self.check_webhook(
|
||||
"customer_updated__account_balance",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_customer_discount_created(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "Discount created ([25.5% off](https://dashboard.stripe.com/coupons/25_00000000000000))."
|
||||
self.check_webhook(
|
||||
"customer_discount_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_invoice_payment_failed(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = (
|
||||
"[Invoice](https://dashboard.stripe.com/invoices/in_00000000000000) payment failed"
|
||||
)
|
||||
self.check_webhook(
|
||||
"invoice_payment_failed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_invoice_created(self) -> None:
|
||||
expected_topic_name = "cus_HH97asvHvaYQYp"
|
||||
expected_message = """
|
||||
[Invoice](https://dashboard.stripe.com/invoices/in_1GpmuuHLwdCOCoR7ghzQDQLW) created (manual)
|
||||
Total: 0.00 INR
|
||||
Amount due: 0.00 INR
|
||||
""".strip()
|
||||
self.check_webhook("invoice_created", expected_topic_name, expected_message)
|
||||
|
||||
def test_invoiceitem_created(self) -> None:
|
||||
expected_topic_name = "cus_00000000000000"
|
||||
expected_message = "[Invoice item](https://dashboard.stripe.com/invoiceitems/ii_00000000000000) created for 10.00 CAD"
|
||||
self.check_webhook(
|
||||
"invoiceitem_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_invoice_paid(self) -> None:
|
||||
expected_topic_name = "cus_FDmrSwQt9Fck5M"
|
||||
expected_message = "[Invoice](https://dashboard.stripe.com/invoices/in_1EjLINHuGUuNWDDZjDf2WNqd) is now paid"
|
||||
self.check_webhook(
|
||||
"invoice_updated__paid",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_refund_event(self) -> None:
|
||||
expected_topic_name = "refunds"
|
||||
expected_message = "A [refund](https://dashboard.stripe.com/refunds/re_1Gib6ZHLwdCOCoR7VrzCnXlj) for a [charge](https://dashboard.stripe.com/charges/ch_1Gib61HLwdCOCoR71rnkccye) of 300000.00 INR was updated."
|
||||
self.check_webhook("refund_event", expected_topic_name, expected_message)
|
||||
|
||||
def test_pseudo_refund_event(self) -> None:
|
||||
expected_topic_name = "refunds"
|
||||
expected_message = "A [refund](https://dashboard.stripe.com/refunds/pyr_abcde12345ABCDF) for a [payment](https://dashboard.stripe.com/payments/py_abcde12345ABCDG) of 12.34 EUR was updated."
|
||||
self.check_webhook("pseudo_refund_event", expected_topic_name, expected_message)
|
||||
|
||||
@patch("zerver.webhooks.stripe.view.check_send_webhook_message")
|
||||
def test_account_updated_without_previous_attributes_ignore(
|
||||
self, check_send_webhook_message_mock: MagicMock
|
||||
) -> None:
|
||||
self.url = self.build_webhook_url()
|
||||
payload = self.get_body("account_updated_without_previous_attributes")
|
||||
result = self.client_post(self.url, payload, content_type="application/json")
|
||||
self.assertFalse(check_send_webhook_message_mock.called)
|
||||
self.assert_json_success(result)
|
||||
385
zerver/webhooks/stripe/view.py
Normal file
385
zerver/webhooks/stripe/view.py
Normal file
@@ -0,0 +1,385 @@
|
||||
# Webhooks for external integrations.
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.decorator import webhook_view
|
||||
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import datetime_to_global_time, timestamp_to_datetime
|
||||
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
||||
from zerver.lib.validator import WildValue, check_bool, check_int, check_none_or, check_string
|
||||
from zerver.lib.webhooks.common import check_send_webhook_message
|
||||
from zerver.models import UserProfile
|
||||
|
||||
|
||||
class SuppressedEventError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotImplementedEventTypeError(SuppressedEventError):
|
||||
pass
|
||||
|
||||
|
||||
ALL_EVENT_TYPES = [
|
||||
"charge.dispute.closed",
|
||||
"charge.dispute.created",
|
||||
"charge.failed",
|
||||
"charge.succeeded",
|
||||
"customer.created",
|
||||
"customer.deleted",
|
||||
"customer.discount.created",
|
||||
"customer.subscription.created",
|
||||
"customer.subscription.deleted",
|
||||
"customer.subscription.trial_will_end",
|
||||
"customer.subscription.updated",
|
||||
"customer.updated",
|
||||
"invoice.created",
|
||||
"invoice.updated",
|
||||
"invoice.payment_failed",
|
||||
"invoiceitem.created",
|
||||
"charge.refund.updated",
|
||||
]
|
||||
|
||||
|
||||
@webhook_view("Stripe", all_event_types=ALL_EVENT_TYPES)
|
||||
@typed_endpoint
|
||||
def api_stripe_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
stream: str = "test",
|
||||
) -> HttpResponse:
|
||||
try:
|
||||
topic_name, body = topic_and_body(payload)
|
||||
except SuppressedEventError: # nocoverage
|
||||
return json_success(request)
|
||||
check_send_webhook_message(
|
||||
request, user_profile, topic_name, body, payload["type"].tame(check_string)
|
||||
)
|
||||
return json_success(request)
|
||||
|
||||
|
||||
def topic_and_body(payload: WildValue) -> tuple[str, str]:
|
||||
event_type = payload["type"].tame(
|
||||
check_string
|
||||
) # invoice.created, customer.subscription.created, etc
|
||||
if len(event_type.split(".")) == 3:
|
||||
category, resource, event = event_type.split(".")
|
||||
else:
|
||||
resource, event = event_type.split(".")
|
||||
category = resource
|
||||
|
||||
object_ = payload["data"]["object"] # The full, updated Stripe object
|
||||
|
||||
# Set the topic to the customer_id when we can
|
||||
topic_name = ""
|
||||
customer_id = object_.get("customer").tame(check_none_or(check_string))
|
||||
if customer_id is not None:
|
||||
# Running into the 60 character topic limit.
|
||||
# topic = '[{}](https://dashboard.stripe.com/customers/{})' % (customer_id, customer_id)
|
||||
topic_name = customer_id
|
||||
body = None
|
||||
|
||||
def charge_object_type(charge_id: str) -> str:
|
||||
# Legacy ACH-style payments report object_type "charge" but use a "py_" id prefix.
|
||||
return "payment" if charge_id.startswith("py_") else "charge"
|
||||
|
||||
def update_string(blacklist: Sequence[str] = []) -> str:
|
||||
assert "previous_attributes" in payload["data"]
|
||||
previous_attributes = set(payload["data"]["previous_attributes"].keys()).difference(
|
||||
blacklist
|
||||
)
|
||||
if not previous_attributes: # nocoverage
|
||||
raise SuppressedEventError
|
||||
return "".join(
|
||||
"\n* "
|
||||
+ attribute.replace("_", " ").capitalize()
|
||||
+ " is now "
|
||||
+ stringify(object_[attribute].value)
|
||||
for attribute in sorted(previous_attributes)
|
||||
)
|
||||
|
||||
def default_body(update_blacklist: Sequence[str] = []) -> str:
|
||||
id = object_["id"].tame(check_string)
|
||||
type = object_["object"].tame(check_string)
|
||||
body = "{resource} {verbed}".format(
|
||||
resource=linkified_id(id, type),
|
||||
verbed=event.replace("_", " "),
|
||||
)
|
||||
if event == "updated":
|
||||
return body + update_string(blacklist=update_blacklist)
|
||||
return body
|
||||
|
||||
if category == "account": # nocoverage
|
||||
if resource == "account":
|
||||
if event == "updated":
|
||||
if "previous_attributes" not in payload["data"]:
|
||||
raise SuppressedEventError
|
||||
topic_name = "account updates"
|
||||
body = update_string()
|
||||
else:
|
||||
# Part of Stripe Connect
|
||||
raise NotImplementedEventTypeError
|
||||
if category == "application_fee": # nocoverage
|
||||
# Part of Stripe Connect
|
||||
raise NotImplementedEventTypeError
|
||||
if category == "balance": # nocoverage
|
||||
# Not that interesting to most businesses, I think
|
||||
raise NotImplementedEventTypeError
|
||||
if category == "charge":
|
||||
if resource == "charge":
|
||||
if not topic_name: # only in legacy fixtures
|
||||
topic_name = "charges"
|
||||
object_id = object_["id"].tame(check_string)
|
||||
charge_type = charge_object_type(object_id)
|
||||
body = "{resource} for {amount} {verbed}".format(
|
||||
resource=linkified_id(object_id, charge_type),
|
||||
amount=amount_string(
|
||||
object_["amount"].tame(check_int), object_["currency"].tame(check_string)
|
||||
),
|
||||
verbed=event,
|
||||
)
|
||||
if object_["failure_code"]: # nocoverage
|
||||
body += ". Failure code: {}".format(object_["failure_code"].tame(check_string))
|
||||
if resource == "dispute":
|
||||
topic_name = "disputes"
|
||||
body = default_body() + ". Current status: {status}.".format(
|
||||
status=object_["status"].tame(check_string).replace("_", " ")
|
||||
)
|
||||
if resource == "refund":
|
||||
topic_name = "refunds"
|
||||
id = object_["id"].tame(check_string)
|
||||
type = object_["object"].tame(check_string)
|
||||
charge_id = object_["charge"].tame(check_string)
|
||||
charge_type = charge_object_type(charge_id)
|
||||
body = "A {resource} for a {charge} of {amount} was updated.".format(
|
||||
resource=linkified_id(id, type, lower=True),
|
||||
charge=linkified_id(charge_id, charge_type, lower=True),
|
||||
amount=amount_string(
|
||||
object_["amount"].tame(check_int), object_["currency"].tame(check_string)
|
||||
),
|
||||
)
|
||||
if category == "checkout_beta": # nocoverage
|
||||
# Not sure what this is
|
||||
raise NotImplementedEventTypeError
|
||||
if category == "coupon": # nocoverage
|
||||
# Not something that likely happens programmatically
|
||||
raise NotImplementedEventTypeError
|
||||
if category == "customer":
|
||||
if resource == "customer":
|
||||
# Running into the 60 character topic limit.
|
||||
# topic = '[{}](https://dashboard.stripe.com/customers/{})' % (object_['id'], object_['id'])
|
||||
topic_name = object_["id"].tame(check_string)
|
||||
body = default_body(update_blacklist=["delinquent", "currency", "default_source"])
|
||||
if event == "created":
|
||||
if object_["email"]:
|
||||
body += "\nEmail: {}".format(object_["email"].tame(check_string))
|
||||
if object_["metadata"]: # nocoverage
|
||||
for key, value in object_["metadata"].items():
|
||||
body += f"\n{key}: {value.tame(check_string)}"
|
||||
if resource == "discount":
|
||||
body = "Discount {verbed} ([{coupon_name}]({coupon_url})).".format(
|
||||
verbed=event.replace("_", " "),
|
||||
coupon_name=object_["coupon"]["name"].tame(check_string),
|
||||
coupon_url="https://dashboard.stripe.com/{}/{}".format(
|
||||
"coupons", object_["coupon"]["id"].tame(check_string)
|
||||
),
|
||||
)
|
||||
if resource == "source": # nocoverage
|
||||
body = default_body()
|
||||
if resource == "subscription":
|
||||
body = default_body()
|
||||
if event == "trial_will_end":
|
||||
DAY = 60 * 60 * 24 # seconds in a day
|
||||
# Basically always three: https://stripe.com/docs/api/python#event_types
|
||||
body += " in {days} days".format(
|
||||
days=int((object_["trial_end"].tame(check_int) - time.time() + DAY // 2) // DAY)
|
||||
)
|
||||
if event == "created":
|
||||
if object_["plan"]:
|
||||
nickname = object_["plan"]["nickname"].tame(check_none_or(check_string))
|
||||
if nickname is not None:
|
||||
body += "\nPlan: [{plan_nickname}](https://dashboard.stripe.com/plans/{plan_id})".format(
|
||||
plan_nickname=object_["plan"]["nickname"].tame(check_string),
|
||||
plan_id=object_["plan"]["id"].tame(check_string),
|
||||
)
|
||||
else:
|
||||
body += "\nPlan: https://dashboard.stripe.com/plans/{plan_id}".format(
|
||||
plan_id=object_["plan"]["id"].tame(check_string),
|
||||
)
|
||||
if object_["quantity"]:
|
||||
body += "\nQuantity: {}".format(object_["quantity"].tame(check_int))
|
||||
if "billing" in object_: # nocoverage
|
||||
body += "\nBilling method: {}".format(
|
||||
object_["billing"].tame(check_string).replace("_", " ")
|
||||
)
|
||||
if category == "file": # nocoverage
|
||||
topic_name = "files"
|
||||
body = default_body() + " ({purpose}). \nTitle: {title}".format(
|
||||
purpose=object_["purpose"].tame(check_string).replace("_", " "),
|
||||
title=object_["title"].tame(check_string),
|
||||
)
|
||||
if category == "invoice":
|
||||
if event == "upcoming": # nocoverage
|
||||
body = "Upcoming invoice created"
|
||||
elif (
|
||||
event == "updated"
|
||||
and payload["data"]["previous_attributes"].get("paid").tame(check_none_or(check_bool))
|
||||
is False
|
||||
and object_["paid"].tame(check_bool) is True
|
||||
and object_["amount_paid"].tame(check_int) != 0
|
||||
and object_["amount_remaining"].tame(check_int) == 0
|
||||
):
|
||||
# We are taking advantage of logical AND short circuiting here since we need the else
|
||||
# statement below.
|
||||
object_id = object_["id"].tame(check_string)
|
||||
invoice_link = f"https://dashboard.stripe.com/invoices/{object_id}"
|
||||
body = f"[Invoice]({invoice_link}) is now paid"
|
||||
else:
|
||||
body = default_body(
|
||||
update_blacklist=[
|
||||
"lines",
|
||||
"description",
|
||||
"number",
|
||||
"finalized_at",
|
||||
"status_transitions",
|
||||
"payment_intent",
|
||||
]
|
||||
)
|
||||
if event == "created":
|
||||
# Could potentially add link to invoice PDF here
|
||||
body += " ({reason})\nTotal: {total}\nAmount due: {due}".format(
|
||||
reason=object_["billing_reason"].tame(check_string).replace("_", " "),
|
||||
total=amount_string(
|
||||
object_["total"].tame(check_int), object_["currency"].tame(check_string)
|
||||
),
|
||||
due=amount_string(
|
||||
object_["amount_due"].tame(check_int), object_["currency"].tame(check_string)
|
||||
),
|
||||
)
|
||||
if category == "invoiceitem":
|
||||
body = default_body(update_blacklist=["description", "invoice"])
|
||||
if event == "created":
|
||||
body += " for {amount}".format(
|
||||
amount=amount_string(
|
||||
object_["amount"].tame(check_int), object_["currency"].tame(check_string)
|
||||
)
|
||||
)
|
||||
if category.startswith("issuing"): # nocoverage
|
||||
# Not implemented
|
||||
raise NotImplementedEventTypeError
|
||||
if category.startswith("order"): # nocoverage
|
||||
# Not implemented
|
||||
raise NotImplementedEventTypeError
|
||||
if category in [
|
||||
"payment_intent",
|
||||
"payout",
|
||||
"plan",
|
||||
"product",
|
||||
"recipient",
|
||||
"reporting",
|
||||
"review",
|
||||
"sigma",
|
||||
"sku",
|
||||
"source",
|
||||
"subscription_schedule",
|
||||
"topup",
|
||||
"transfer",
|
||||
]: # nocoverage
|
||||
# Not implemented. In theory doing something like
|
||||
# body = default_body()
|
||||
# may not be hard for some of these
|
||||
raise NotImplementedEventTypeError
|
||||
|
||||
if body is None:
|
||||
raise UnsupportedWebhookEventTypeError(event_type)
|
||||
return (topic_name, body)
|
||||
|
||||
|
||||
def amount_string(amount: int, currency: str) -> str:
|
||||
zero_decimal_currencies = [
|
||||
"bif",
|
||||
"djf",
|
||||
"jpy",
|
||||
"krw",
|
||||
"pyg",
|
||||
"vnd",
|
||||
"xaf",
|
||||
"xpf",
|
||||
"clp",
|
||||
"gnf",
|
||||
"kmf",
|
||||
"mga",
|
||||
"rwf",
|
||||
"vuv",
|
||||
"xof",
|
||||
]
|
||||
if currency in zero_decimal_currencies:
|
||||
decimal_amount = str(amount) # nocoverage
|
||||
else:
|
||||
decimal_amount = f"{float(amount) * 0.01:.02f}"
|
||||
|
||||
if currency == "usd": # nocoverage
|
||||
return "$" + decimal_amount
|
||||
return decimal_amount + f" {currency.upper()}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DisplayInfo:
|
||||
name: str
|
||||
url_prefix: str | None = None
|
||||
|
||||
|
||||
STRIPE_OBJECT_TYPES: dict[str, DisplayInfo] = {
|
||||
# CORE RESOURCES
|
||||
"charge": DisplayInfo("Charge", "charges"),
|
||||
"customer": DisplayInfo("Customer", "customers"),
|
||||
"dispute": DisplayInfo("Dispute", "disputes"),
|
||||
"file": DisplayInfo("File", "files"),
|
||||
"file_link": DisplayInfo("File link", "file_links"),
|
||||
"payment_intent": DisplayInfo("Payment intent", "payment_intents"),
|
||||
"payout": DisplayInfo("Payout", "payouts"),
|
||||
"product": DisplayInfo("Product", "products"),
|
||||
"refund": DisplayInfo("Refund", "refunds"),
|
||||
"token": DisplayInfo("Token", "tokens"),
|
||||
# PAYMENT METHODS
|
||||
# Payment methods have URL prefixes like /customers/cus_id/sources.
|
||||
"bank_account": DisplayInfo("Bank account"),
|
||||
"card": DisplayInfo("Card"),
|
||||
"source": DisplayInfo("Source"),
|
||||
# BILLING
|
||||
# Coupons have a configurable id, but the URL prefix is /coupons.
|
||||
# It's not clear if discounts have a URL.
|
||||
"invoice": DisplayInfo("Invoice", "invoices"),
|
||||
"invoiceitem": DisplayInfo("Invoice item", "invoiceitems"),
|
||||
# Products are covered in core resources.
|
||||
# Plans have a configurable id, though by default they are created with this pattern:
|
||||
# 'plan': ('Plan', 'plans').
|
||||
"subscription": DisplayInfo("Subscription", "subscriptions"),
|
||||
"subscription_item": DisplayInfo("Subscription item", "subscription_items"),
|
||||
# Usage records seem to have URL prefixes like /subscription_items/si_id/usage_record_summaries.
|
||||
"usage_record": DisplayInfo("Usage record"),
|
||||
# UNDOCUMENTED
|
||||
"payment": DisplayInfo("Payment", "payments"),
|
||||
# Connect, Fraud, Orders, etc not implemented.
|
||||
}
|
||||
|
||||
|
||||
def linkified_id(id: str, type: str, lower: bool = False) -> str:
|
||||
display_info = STRIPE_OBJECT_TYPES[type]
|
||||
name = display_info.name
|
||||
if lower:
|
||||
name = name.lower()
|
||||
if display_info.url_prefix is None: # nocoverage
|
||||
return name
|
||||
return f"[{name}](https://dashboard.stripe.com/{display_info.url_prefix}/{id})"
|
||||
|
||||
|
||||
def stringify(value: object) -> str:
|
||||
if isinstance(value, int) and value > 1500000000 and value < 2000000000:
|
||||
return datetime_to_global_time(timestamp_to_datetime(value))
|
||||
return str(value)
|
||||
Reference in New Issue
Block a user