Created
September 11, 2013 01:37
-
-
Save philfreo/6518326 to your computer and use it in GitHub Desktop.
Stripe invoice.created webhook handling for subscription billing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def stripe_invoice_created_webhook(stripe_event): | |
"""Called by Stripe when an invoice is created so we can log it and tell Stripe about any charges to add.""" | |
stripe_event_invoice = stripe_event['data']['object'] | |
stripe_invoice = stripe.Invoice.retrieve(stripe_event_invoice['id']) | |
orgs = Organization.objects.filter(status=OrganizationStatus.Paying, stripe_customer_id=stripe_invoice.customer) | |
for org in orgs: | |
if org.manual_billing: | |
continue | |
""" | |
We can only add Stripe invoice items to an "open" (non-closed) Stripe invoice. | |
- https://support.stripe.com/questions/metered-subscription-billing | |
- https://support.stripe.com/questions/when-is-an-invoice-open-for-modification | |
An invoice will be sent to us as "closed" for different reasons. Various cases to consider... | |
""" | |
# 1) A not-yet-expired trial just added a subscription. A $0 paid/closed invoice is then created. | |
# => don't log this invoice at all | |
if stripe_invoice.closed and stripe_invoice.paid and stripe_invoice.total == 0: | |
return | |
# 2) Upon the renewal date of a subscription that was previously considered 'unpaid' the invoice | |
# the invoice will be 'closed' (this is dumb to me) an unpaid. | |
# => tell stripe to reopen it, then log invoice, generate and send stripe invoice items | |
if stripe_invoice.closed and not stripe_invoice.paid: | |
stripe_invoice.closed = False | |
stripe_invoice.save() | |
# no return here because we want to fall through to 'open' case | |
# 3) A subscription's trial ends, causing another invoice created, this time *open*, OR | |
# 4) A regular renewal event occurs creating an *open* invoice. | |
# => log invoice, generate and send to stripe invoice items based on current memberships | |
if not stripe_invoice.closed: | |
invoice = Invoice.objects.create(organization=org, stripe_id=stripe_invoice.id) | |
org.generate_invoice_items(invoice) | |
return | |
# 5) An already-expired trial just added a subscription and got charged immediately upon | |
# the subscription creation, causing the webhook notification to come after the invoice is | |
# already paid/closed. Amount > $0. | |
# => log invoice, update our own local pending invoiceitems. don't send stripe anything. | |
if stripe_invoice.closed and stripe_invoice.paid and stripe_invoice.total > 0: | |
pending_items = InvoiceItem.objects.filter(organization=org, invoice=None) | |
if pending_items: | |
# link the initial InvoiceItems (generated during the subscription creation) to the created Invoice | |
invoice = Invoice.objects.create(organization=org, stripe_id=stripe_invoice.id) | |
pending_items.update(set__invoice=invoice) | |
return | |
mail_exception(subject='Unexpected Stripe Invoice Webhook', context={'stripe_event_invoice': stripe_event_invoice}) | |
@app.route('/stripe_webhook/', methods=['POST']) | |
def stripe_webhook(): | |
"""Called by Stripe on various events""" | |
stripe_event = request.json | |
if stripe_event['type'] == 'invoice.created': | |
stripe_invoice_created_webhook(stripe_event) | |
# Careful that we return a 200 | |
return '' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment