Repository: dj-stripe/dj-stripe

Issue:
Default max digits of `StripeCurrencyField` should be 8, not 7
Trying to do a charge of 999,999.99 results in a `decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]` when a webhook is received and we attempt to create the `Charge` object.


Discussion:
Good catch. See also https://support.stripe.com/questions/what-is-the-maximum-amount-i-can-charge-with-stripe.

@jameshiew Let me know if you'd like to submit a PR or if you want me to add it to my list (which might take a while)
@kavdev unfortunately I won't have time to do much dj-stripe stuff anytime soon (i.e. in the next few months), if it's still around then I can take a crack at it :)
@jameshiew: no worries, I'm in the same boat. We'll soon be implementing a 20% time type of deal at work, so I'll hopefully have more time to dedicate to this project in the coming months as well.
Taking this

Resolving patch:
diff --git a/djstripe/fields.py b/djstripe/fields.py
--- a/djstripe/fields.py
+++ b/djstripe/fields.py
@@ -113,7 +113,7 @@ def __init__(self, *args, **kwargs):
         """Assign default args to this field."""
         defaults = {
             'decimal_places': 2,
-            'max_digits': 7,
+            'max_digits': 8,
         }
         defaults.update(kwargs)
         super(StripeCurrencyField, self).__init__(*args, **defaults)
diff --git a/djstripe/migrations/0024_auto_20170308_0757.py b/djstripe/migrations/0024_auto_20170308_0757.py
new file mode 100644
--- /dev/null
+++ b/djstripe/migrations/0024_auto_20170308_0757.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.6 on 2017-03-08 07:57
+from __future__ import unicode_literals
+
+from django.db import migrations
+import djstripe.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('djstripe', '0023_auto_20170307_0937'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='charge',
+            name='amount',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='Amount charged.', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='charge',
+            name='amount_refunded',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='Amount refunded (can be less than the amount attribute on the charge if a partial refund was issued).', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='charge',
+            name='fee',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='invoice',
+            name='amount_due',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text="Final amount due at this time for this invoice. If the invoice's total is smaller than the minimum charge amount, for example, or if there is account credit that can be applied to the invoice, the amount_due may be 0. If there is a positive starting_balance for the invoice (the customer owes money), the amount_due will also take that into account. The charge that gets generated for the invoice will be for the amount specified in amount_due.", max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='invoice',
+            name='application_fee',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text="The fee in cents that will be applied to the invoice and transferred to the application owner's Stripe account when the invoice is paid.", max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='invoice',
+            name='subtotal',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='Only set for upcoming invoices that preview prorations. The time used to calculate prorations.', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='invoice',
+            name='tax',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='The amount of tax included in the total, calculated from ``tax_percent`` and the subtotal. If no ``tax_percent`` is defined, this value will be null.', max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='invoice',
+            name='total',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, max_digits=8, verbose_name='Total after discount.'),
+        ),
+        migrations.AlterField(
+            model_name='invoiceitem',
+            name='amount',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='Amount invoiced.', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='plan',
+            name='amount',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='Amount to be charged on the interval specified.', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='adjustment_fees',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='adjustment_gross',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='amount',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='The amount transferred', max_digits=8),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='amount_reversed',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, help_text='The amount reversed (can be less than the amount attribute on the transfer if a partial reversal was issued).', max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='charge_fees',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='charge_gross',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='collected_fee_gross',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='fee',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='net',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='refund_fees',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='refund_gross',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+        migrations.AlterField(
+            model_name='transfer',
+            name='validation_fees',
+            field=djstripe.fields.StripeCurrencyField(decimal_places=2, default=None, max_digits=8, null=True),
+        ),
+    ]


Test patch:
diff --git a/tests/test_charge.py b/tests/test_charge.py
--- a/tests/test_charge.py
+++ b/tests/test_charge.py
@@ -65,6 +65,23 @@ def test_sync_from_stripe_data(self, default_account_mock):
         self.assertEqual("card_16YKQh2eZvKYlo2Cblc5Feoo", charge.source_stripe_id)
         self.assertEqual("card", charge.source_type)
 
+    @patch("djstripe.models.Account.get_default_account")
+    def test_sync_from_stripe_data_max_amount(self, default_account_mock):
+        default_account_mock.return_value = self.account
+
+        fake_charge_copy = deepcopy(FAKE_CHARGE)
+        # https://support.stripe.com/questions/what-is-the-maximum-amount-i-can-charge-with-stripe
+        fake_charge_copy.update({"amount": 99999999})
+
+        charge = Charge.sync_from_stripe_data(fake_charge_copy)
+
+        self.assertEqual(Decimal("999999.99"), charge.amount)
+        self.assertEqual(True, charge.paid)
+        self.assertEqual(False, charge.refunded)
+        self.assertEqual(True, charge.captured)
+        self.assertEqual(False, charge.disputed)
+        self.assertEqual(0, charge.amount_refunded)
+
     @patch("djstripe.models.Account.get_default_account")
     def test_sync_from_stripe_data_unsupported_source(self, default_account_mock):
         default_account_mock.return_value = self.account