-
Notifications
You must be signed in to change notification settings - Fork 286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create invoice item #436
base: original
Are you sure you want to change the base?
Create invoice item #436
Changes from 12 commits
a03b657
bae60ab
c8acb98
c0acca1
3226b70
8011951
b9e56ed
810a066
4501b0d
54bf5c1
6c721ea
95145cd
9e472e9
9c1660c
04a6d1c
3211821
975bb9b
e111401
b586d3b
027d1e1
27bd049
01c88b6
0c2ced3
2e9b3ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,9 +63,116 @@ def pay(invoice, send_receipt=True): | |
return False | ||
|
||
|
||
def paid(invoice): | ||
""" | ||
Sometimes customers may want to pay with payment methods outside of Stripe, such as check. | ||
In these situations, Stripe still allows you to keep track of the payment status of your invoices. | ||
Once you receive an invoice payment from a customer outside of Stripe, you can manually | ||
mark their invoices as paid. | ||
|
||
Args: | ||
invoice: the invoice object to close | ||
""" | ||
if not invoice.paid: | ||
stripe_invoice = invoice.stripe_invoice | ||
stripe_invoice.paid = True | ||
stripe_invoice_ = stripe_invoice.save() | ||
sync_invoice_from_stripe_data(stripe_invoice_) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we have to worry about creating a new variable. Let's just: stripe_invoice.save() |
||
|
||
|
||
def forgive(invoice): | ||
""" | ||
Forgiving an invoice instructs us to update the subscription status as if the invoice were | ||
successfully paid. Once an invoice has been forgiven, it cannot be unforgiven or reopened. | ||
|
||
Args: | ||
invoice: the invoice object to close | ||
""" | ||
if not invoice.paid: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/paid/forgiven/ ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or both? |
||
stripe_invoice = invoice.stripe_invoice | ||
stripe_invoice.forgiven = True | ||
stripe_invoice_ = stripe_invoice.save() | ||
sync_invoice_from_stripe_data(stripe_invoice_) | ||
|
||
|
||
def close(invoice): | ||
""" | ||
Cause an invoice to be closed; This prevents Stripe from automatically charging your customer for the invoice amount. | ||
|
||
Args: | ||
invoice: the invoice object to close | ||
""" | ||
if not invoice.closed: | ||
stripe_invoice = invoice.stripe_invoice | ||
stripe_invoice.closed = True | ||
stripe_invoice_ = stripe_invoice.save() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, let's just call |
||
sync_invoice_from_stripe_data(stripe_invoice_) | ||
|
||
|
||
def open(invoice): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's rename this to |
||
""" | ||
(re)-open a closed invoice (which is hold for review) | ||
|
||
Args: | ||
invoice: the invoice object to open | ||
""" | ||
if invoice.closed: | ||
stripe_invoice = invoice.stripe_invoice | ||
stripe_invoice.closed = False | ||
stripe_invoice_ = stripe_invoice.save() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, let's just call |
||
sync_invoice_from_stripe_data(stripe_invoice_) | ||
|
||
|
||
def create_invoice_item(customer, invoice, subscription, amount, currency, description, metadata=None): | ||
""" | ||
:param customer: The pinax-stripe Customer | ||
:param invoice: | ||
:param subscription: | ||
:param amount: | ||
:param currency: | ||
:param description: | ||
:param metadata: Any optional metadata that is attached to the invoice item | ||
:return: | ||
""" | ||
stripe_invoice_item = stripe.InvoiceItem.create( | ||
customer=customer.stripe_id, | ||
amount=utils.convert_amount_for_api(amount, currency), | ||
currency=currency, | ||
description=description, | ||
invoice=invoice.stripe_id, | ||
discountable=True, | ||
metadata=metadata, | ||
subscription=subscription.stripe_id, | ||
) | ||
|
||
period_end = utils.convert_tstamp(stripe_invoice_item["period"], "end") | ||
period_start = utils.convert_tstamp(stripe_invoice_item["period"], "start") | ||
|
||
# We can safely take the plan from the subscription here because we are creating a new invoice item for this new invoice that is applicable | ||
# to the current subscription/current plan. | ||
plan = subscription.plan | ||
|
||
defaults = dict( | ||
amount=utils.convert_amount_for_db(stripe_invoice_item["amount"], stripe_invoice_item["currency"]), | ||
currency=stripe_invoice_item["currency"], | ||
proration=stripe_invoice_item["proration"], | ||
description=description, | ||
line_type=stripe_invoice_item["object"], | ||
plan=plan, | ||
period_start=period_start, | ||
period_end=period_end, | ||
quantity=stripe_invoice_item.get("quantity"), | ||
subscription=subscription, | ||
) | ||
inv_item, inv_item_created = invoice.items.get_or_create( | ||
stripe_id=stripe_invoice_item["id"], | ||
defaults=defaults | ||
) | ||
return utils.update_with_defaults(inv_item, defaults, inv_item_created) | ||
|
||
def sync_invoice_from_stripe_data(stripe_invoice, send_receipt=settings.PINAX_STRIPE_SEND_EMAIL_RECEIPTS): | ||
""" | ||
Syncronizes a local invoice with data from the Stripe API | ||
Synchronizes a local invoice with data from the Stripe API | ||
|
||
Args: | ||
stripe_invoice: data that represents the invoice from the Stripe API | ||
|
@@ -96,6 +203,7 @@ def sync_invoice_from_stripe_data(stripe_invoice, send_receipt=settings.PINAX_ST | |
attempt_count=stripe_invoice["attempt_count"], | ||
amount_due=utils.convert_amount_for_db(stripe_invoice["amount_due"], stripe_invoice["currency"]), | ||
closed=stripe_invoice["closed"], | ||
forgiven=stripe_invoice["forgiven"], | ||
paid=stripe_invoice["paid"], | ||
period_end=period_end, | ||
period_start=period_start, | ||
|
@@ -108,7 +216,13 @@ def sync_invoice_from_stripe_data(stripe_invoice, send_receipt=settings.PINAX_ST | |
charge=charge, | ||
subscription=subscription, | ||
receipt_number=stripe_invoice["receipt_number"] or "", | ||
metadata=stripe_invoice["metadata"] | ||
) | ||
if "billing" in stripe_invoice: | ||
defaults.update({ | ||
"billing": stripe_invoice["billing"], | ||
"due_date": utils.convert_tstamp(stripe_invoice, "due_date") if stripe_invoice.get("due_date", None) is not None else None | ||
}) | ||
invoice, created = models.Invoice.objects.get_or_create( | ||
stripe_id=stripe_invoice["id"], | ||
defaults=defaults | ||
|
@@ -134,6 +248,13 @@ def sync_invoices_for_customer(customer): | |
sync_invoice_from_stripe_data(invoice, send_receipt=False) | ||
|
||
|
||
def sync_invoice(invoice): | ||
""" | ||
Syncronizes a specific invoice | ||
""" | ||
sync_invoice_from_stripe_data(invoice.stripe_invoice, send_receipt=False) | ||
|
||
|
||
def sync_invoice_items(invoice, items): | ||
""" | ||
Syncronizes all invoice line items for a particular invoice | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.10.7 on 2017-10-19 13:21 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import jsonfield.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('pinax_stripe', '0010_connect'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='invoice', | ||
name='billing', | ||
field=models.CharField(default='charge_automatically', max_length=32), | ||
), | ||
migrations.AddField( | ||
model_name='invoice', | ||
name='due_date', | ||
field=models.DateTimeField(blank=True, null=True), | ||
), | ||
migrations.AddField( | ||
model_name='invoice', | ||
name='forgiven', | ||
field=models.BooleanField(default=False), | ||
), | ||
migrations.AddField( | ||
model_name='invoice', | ||
name='metadata', | ||
field=jsonfield.fields.JSONField(null=True), | ||
), | ||
migrations.AddField( | ||
model_name='subscription', | ||
name='billing', | ||
field=models.CharField(default='charge_automatically', max_length=32), | ||
), | ||
migrations.AddField( | ||
model_name='subscription', | ||
name='days_until_due', | ||
field=models.IntegerField(blank=True, default=None, null=True), | ||
), | ||
migrations.AlterField( | ||
model_name='subscription', | ||
name='application_fee_percent', | ||
field=models.DecimalField(blank=True, decimal_places=2, default=None, max_digits=3, null=True), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -228,7 +228,7 @@ class Subscription(StripeObject): | |
STATUS_CURRENT = ["trialing", "active"] | ||
|
||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE) | ||
application_fee_percent = models.DecimalField(decimal_places=2, max_digits=3, default=None, null=True) | ||
application_fee_percent = models.DecimalField(decimal_places=2, max_digits=3, default=None, blank=True, null=True) | ||
cancel_at_period_end = models.BooleanField(default=False) | ||
canceled_at = models.DateTimeField(blank=True, null=True) | ||
current_period_end = models.DateTimeField(blank=True, null=True) | ||
|
@@ -240,6 +240,8 @@ class Subscription(StripeObject): | |
status = models.CharField(max_length=25) # trialing, active, past_due, canceled, or unpaid | ||
trial_end = models.DateTimeField(blank=True, null=True) | ||
trial_start = models.DateTimeField(blank=True, null=True) | ||
billing = models.CharField(max_length=32, default=u'charge_automatically') # charge_automatically or send_invoice | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an enum/choices to avoid the hard coded string. |
||
days_until_due = models.IntegerField(default=None, blank=True, null=True) | ||
|
||
@property | ||
def stripe_subscription(self): | ||
|
@@ -278,6 +280,7 @@ class Invoice(StripeObject): | |
statement_descriptor = models.TextField(blank=True) | ||
currency = models.CharField(max_length=10, default="usd") | ||
closed = models.BooleanField(default=False) | ||
forgiven = models.BooleanField(default=False) | ||
description = models.TextField(blank=True) | ||
paid = models.BooleanField(default=False) | ||
receipt_number = models.TextField(blank=True) | ||
|
@@ -289,6 +292,9 @@ class Invoice(StripeObject): | |
total = models.DecimalField(decimal_places=2, max_digits=9) | ||
date = models.DateTimeField() | ||
webhooks_delivered_at = models.DateTimeField(null=True) | ||
billing = models.CharField(max_length=32, default=u'charge_automatically') # charge_automatically or send_invoice | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an enum/choices to avoid the hard coded string. |
||
due_date = models.DateTimeField(null=True, blank=True) | ||
metadata = JSONField(null=True) | ||
|
||
@property | ||
def status(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer this to be a verb. Can we change to
mark_paid
so it's clearer what the function is doing?