billing: Add functions for automatic invoicing of plans.

This commit is contained in:
Rishi Gupta
2019-01-28 13:57:29 -08:00
parent 421cda0e34
commit 4aa2ac1b52
32 changed files with 2066 additions and 2 deletions

View File

@@ -91,6 +91,21 @@ def next_renewal_date(plan: CustomerPlan, event_time: datetime) -> datetime:
periods += 1
return dt
# TODO take downgrade into account
def next_invoice_date(plan: CustomerPlan) -> datetime:
months_per_period = {
CustomerPlan.ANNUAL: 12,
CustomerPlan.MONTHLY: 1,
}[plan.billing_schedule]
if plan.automanage_licenses:
months_per_period = 1
periods = 1
dt = plan.billing_cycle_anchor
while dt <= plan.next_invoice_date:
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
periods += 1
return dt
def renewal_amount(plan: CustomerPlan) -> Optional[int]: # nocoverage: TODO
if plan.fixed_price is not None:
return plan.fixed_price
@@ -356,6 +371,77 @@ def update_license_ledger_if_needed(realm: Realm, event_time: datetime) -> None:
return
update_license_ledger_for_automanaged_plan(realm, plan, event_time)
def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
if plan.invoicing_status == CustomerPlan.STARTED:
raise NotImplementedError('Plan with invoicing_status==STARTED needs manual resolution.')
add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
assert(plan.invoiced_through is not None)
licenses_base = plan.invoiced_through.licenses
invoice_item_created = False
for ledger_entry in LicenseLedger.objects.filter(plan=plan, id__gt=plan.invoiced_through.id,
event_time__lte=event_time).order_by('id'):
price_args = {} # type: Dict[str, int]
if ledger_entry.is_renewal:
if plan.fixed_price is not None:
price_args = {'amount': plan.fixed_price}
else:
assert(plan.price_per_license is not None) # needed for mypy
price_args = {'unit_amount': plan.price_per_license,
'quantity': ledger_entry.licenses}
description = "Zulip Standard - renewal"
elif ledger_entry.licenses != licenses_base:
assert(plan.price_per_license)
last_renewal = LicenseLedger.objects.filter(
plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time) \
.order_by('-id').first().event_time
period_end = next_renewal_date(plan, ledger_entry.event_time)
proration_fraction = (period_end - ledger_entry.event_time) / (period_end - last_renewal)
price_args = {'unit_amount': int(plan.price_per_license * proration_fraction + .5),
'quantity': ledger_entry.licenses - licenses_base}
description = "Additional license ({} - {})".format(
ledger_entry.event_time.strftime('%b %-d, %Y'), period_end.strftime('%b %-d, %Y'))
if price_args:
plan.invoiced_through = ledger_entry
plan.invoicing_status = CustomerPlan.STARTED
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
stripe.InvoiceItem.create(
currency='usd',
customer=plan.customer.stripe_customer_id,
description=description,
discountable=False,
period = {'start': datetime_to_timestamp(ledger_entry.event_time),
'end': datetime_to_timestamp(next_renewal_date(plan, ledger_entry.event_time))},
idempotency_key='ledger_entry:{}'.format(ledger_entry.id),
**price_args)
invoice_item_created = True
plan.invoiced_through = ledger_entry
plan.invoicing_status = CustomerPlan.DONE
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
licenses_base = ledger_entry.licenses
if invoice_item_created:
if plan.charge_automatically:
billing_method = 'charge_automatically'
days_until_due = None
else:
billing_method = 'send_invoice'
days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE
stripe_invoice = stripe.Invoice.create(
auto_advance=True,
billing=billing_method,
customer=plan.customer.stripe_customer_id,
days_until_due=days_until_due,
statement_descriptor='Zulip Standard')
stripe.Invoice.finalize_invoice(stripe_invoice)
plan.next_invoice_date = next_invoice_date(plan)
plan.save(update_fields=['next_invoice_date'])
def invoice_plans_as_needed(event_time: datetime) -> None:
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
invoice_plan(plan, event_time)
def attach_discount_to_realm(user: UserProfile, discount: Decimal) -> None:
customer = Customer.objects.filter(realm=user.realm).first()
if customer is None:

View File

@@ -0,0 +1,39 @@
{
"account_balance": 0,
"created": 1000000000,
"currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"footer": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"object": "customer",
"shipping": null,
"sources": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/customers/cus_NORMALIZED0001/sources"
},
"subscriptions": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
},
"tax_info": null,
"tax_info_verification": null
}

View File

@@ -0,0 +1,72 @@
{
"amount_due": 984000,
"amount_paid": 0,
"amount_remaining": 984000,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": null,
"finalized_at": null,
"footer": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": null,
"lines": {
"data": [
{
"amount": 984000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 123,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "draft",
"subscription": null,
"subtotal": 984000,
"tax": 0,
"tax_percent": null,
"total": 984000,
"webhooks_delivered_at": null
}

View File

@@ -0,0 +1,72 @@
{
"amount_due": 100,
"amount_paid": 0,
"amount_remaining": 100,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": null,
"finalized_at": null,
"footer": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": null,
"lines": {
"data": [
{
"amount": 100,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "draft",
"subscription": null,
"subtotal": 100,
"tax": 0,
"tax_percent": null,
"total": 100,
"webhooks_delivered_at": null
}

View File

@@ -0,0 +1,72 @@
{
"amount_due": 984000,
"amount_paid": 0,
"amount_remaining": 984000,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": 0,
"finalized_at": 1000000000,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
"lines": {
"data": [
{
"amount": 984000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 123,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 984000,
"tax": 0,
"tax_percent": null,
"total": 984000,
"webhooks_delivered_at": 1000000000
}

View File

@@ -0,0 +1,72 @@
{
"amount_due": 100,
"amount_paid": 0,
"amount_remaining": 100,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": 0,
"finalized_at": 1000000000,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
"lines": {
"data": [
{
"amount": 100,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 100,
"tax": 0,
"tax_percent": null,
"total": 100,
"webhooks_delivered_at": 1000000000
}

View File

@@ -0,0 +1,151 @@
{
"data": [
{
"amount_due": 100,
"amount_paid": 0,
"amount_remaining": 100,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": 0,
"finalized_at": 1000000000,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
"lines": {
"data": [
{
"amount": 100,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 100,
"tax": 0,
"tax_percent": null,
"total": 100,
"webhooks_delivered_at": 1000000000
},
{
"amount_due": 984000,
"amount_paid": 0,
"amount_remaining": 984000,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "send_invoice",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1000000000,
"ending_balance": 0,
"finalized_at": 1000000000,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
"lines": {
"data": [
{
"amount": 984000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 123,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"paid": false,
"period_end": 1000000000,
"period_start": 1000000000,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 984000,
"tax": 0,
"tax_percent": null,
"total": 984000,
"webhooks_delivered_at": 1000000000
}
],
"has_more": false,
"object": "list",
"url": "/v1/invoices"
}

View File

@@ -0,0 +1,22 @@
{
"amount": 984000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Zulip Standard",
"discountable": false,
"id": "ii_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 123,
"subscription": null,
"unit_amount": 8000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 100,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"unit_amount": 100
}

View File

@@ -0,0 +1,78 @@
{
"amount": 64000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_1DxdruGh0CmXqmnwJQIcAlGj",
"captured": true,
"created": 1548696358,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"description": "Upgrade to Zulip Standard, $80.0 x 8",
"destination": null,
"dispute": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_1DxdruGh0CmXqmnwSwEPHAI6",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 6,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": null,
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_1BWYgHGh0CmXqmnw/ch_1DxdruGh0CmXqmnwSwEPHAI6/rcpt_EQUr7TAzRbJU3vjRTVdNKpnyUvWTXz2",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_1DxdruGh0CmXqmnwSwEPHAI6/refunds"
},
"review": null,
"shipping": null,
"source": {
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "pass",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "pass",
"brand": "Visa",
"country": "US",
"customer": "cus_EQUrCdXCP2oqIF",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdrtGh0CmXqmnw1LvZ0cL4",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
},
"source_transfer": null,
"statement_descriptor": "Zulip Standard",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -0,0 +1,65 @@
{
"account_balance": 0,
"created": 1548696357,
"currency": null,
"default_source": "card_1DxdrtGh0CmXqmnw1LvZ0cL4",
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_EQUrCdXCP2oqIF",
"invoice_prefix": "81F7A21",
"invoice_settings": {
"custom_fields": null,
"footer": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"object": "customer",
"shipping": null,
"sources": {
"data": [
{
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "pass",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "pass",
"brand": "Visa",
"country": "US",
"customer": "cus_EQUrCdXCP2oqIF",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdrtGh0CmXqmnw1LvZ0cL4",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/customers/cus_EQUrCdXCP2oqIF/sources"
},
"subscriptions": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/customers/cus_EQUrCdXCP2oqIF/subscriptions"
},
"tax_info": null,
"tax_info_verification": null
}

View File

@@ -0,0 +1,92 @@
{
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696360,
"default_source": null,
"description": "",
"discount": null,
"due_date": null,
"ending_balance": null,
"finalized_at": null,
"footer": null,
"hosted_invoice_url": null,
"id": "in_1DxdrwGh0CmXqmnw7x9G2rWd",
"invoice_pdf": null,
"lines": {
"data": [
{
"amount": 64000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"invoice_item": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": -64000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"invoice_item": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1548696359,
"start": 1548696359
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_1DxdrwGh0CmXqmnw7x9G2rWd/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1548699960,
"number": "81F7A21-0001",
"object": "invoice",
"paid": false,
"period_end": 1548696360,
"period_start": 1548696360,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "draft",
"subscription": null,
"subtotal": 0,
"tax": 0,
"tax_percent": null,
"total": 0,
"webhooks_delivered_at": null
}

View File

@@ -0,0 +1,112 @@
{
"amount_due": 96697,
"amount_paid": 0,
"amount_remaining": 96697,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696362,
"default_source": null,
"description": "",
"discount": null,
"due_date": null,
"ending_balance": null,
"finalized_at": null,
"footer": null,
"hosted_invoice_url": null,
"id": "in_1DxdryGh0CmXqmnwqSArnOBA",
"invoice_pdf": null,
"lines": {
"data": [
{
"amount": 7255,
"currency": "usd",
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
"discountable": false,
"id": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"invoice_item": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1360033445
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 72000,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnw183j11lw",
"invoice_item": "ii_1DxdrxGh0CmXqmnw183j11lw",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 9,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 17442,
"currency": "usd",
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"invoice_item": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"proration": false,
"quantity": 3,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 3,
"url": "/v1/invoices/in_1DxdryGh0CmXqmnwqSArnOBA/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1548699962,
"number": "81F7A21-0002",
"object": "invoice",
"paid": false,
"period_end": 1548696362,
"period_start": 1548696362,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "draft",
"subscription": null,
"subtotal": 96697,
"tax": 0,
"tax_percent": null,
"total": 96697,
"webhooks_delivered_at": null
}

View File

@@ -0,0 +1,92 @@
{
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696360,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1551288360,
"ending_balance": 0,
"finalized_at": 1548696360,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_QWWFlNhg4YyUdnWxVhUvuDs8TZ",
"id": "in_1DxdrwGh0CmXqmnw7x9G2rWd",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_QWWFlNhg4YyUdnWxVhUvuDs8TZ/pdf",
"lines": {
"data": [
{
"amount": 64000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"invoice_item": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": -64000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"invoice_item": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1548696359,
"start": 1548696359
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_1DxdrwGh0CmXqmnw7x9G2rWd/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "81F7A21-0001",
"object": "invoice",
"paid": true,
"period_end": 1548696360,
"period_start": 1548696360,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "paid",
"subscription": null,
"subtotal": 0,
"tax": 0,
"tax_percent": null,
"total": 0,
"webhooks_delivered_at": 1548696360
}

View File

@@ -0,0 +1,112 @@
{
"amount_due": 96697,
"amount_paid": 0,
"amount_remaining": 96697,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696362,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1551288363,
"ending_balance": 0,
"finalized_at": 1548696363,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_sT02SUZUJrLO3QmdmKIAQwQi9F",
"id": "in_1DxdryGh0CmXqmnwqSArnOBA",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_sT02SUZUJrLO3QmdmKIAQwQi9F/pdf",
"lines": {
"data": [
{
"amount": 7255,
"currency": "usd",
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
"discountable": false,
"id": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"invoice_item": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1360033445
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 72000,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnw183j11lw",
"invoice_item": "ii_1DxdrxGh0CmXqmnw183j11lw",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 9,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 17442,
"currency": "usd",
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"invoice_item": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"proration": false,
"quantity": 3,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 3,
"url": "/v1/invoices/in_1DxdryGh0CmXqmnwqSArnOBA/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1548699962,
"number": "81F7A21-0002",
"object": "invoice",
"paid": false,
"period_end": 1548696362,
"period_start": 1548696362,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 96697,
"tax": 0,
"tax_percent": null,
"total": 96697,
"webhooks_delivered_at": 1548696362
}

View File

@@ -0,0 +1,211 @@
{
"data": [
{
"amount_due": 96697,
"amount_paid": 0,
"amount_remaining": 96697,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696362,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1551288363,
"ending_balance": 0,
"finalized_at": 1548696363,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_sT02SUZUJrLO3QmdmKIAQwQi9F",
"id": "in_1DxdryGh0CmXqmnwqSArnOBA",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_sT02SUZUJrLO3QmdmKIAQwQi9F/pdf",
"lines": {
"data": [
{
"amount": 7255,
"currency": "usd",
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
"discountable": false,
"id": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"invoice_item": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1360033445
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 72000,
"currency": "usd",
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnw183j11lw",
"invoice_item": "ii_1DxdrxGh0CmXqmnw183j11lw",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 9,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": 17442,
"currency": "usd",
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"invoice_item": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"proration": false,
"quantity": 3,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 3,
"url": "/v1/invoices/in_1DxdryGh0CmXqmnwqSArnOBA/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1548699962,
"number": "81F7A21-0002",
"object": "invoice",
"paid": false,
"period_end": 1548696362,
"period_start": 1548696362,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "open",
"subscription": null,
"subtotal": 96697,
"tax": 0,
"tax_percent": null,
"total": 96697,
"webhooks_delivered_at": 1548696362
},
{
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696360,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1551288360,
"ending_balance": 0,
"finalized_at": 1548696360,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_QWWFlNhg4YyUdnWxVhUvuDs8TZ",
"id": "in_1DxdrwGh0CmXqmnw7x9G2rWd",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_QWWFlNhg4YyUdnWxVhUvuDs8TZ/pdf",
"lines": {
"data": [
{
"amount": 64000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"invoice_item": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": -64000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"invoice_item": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1548696359,
"start": 1548696359
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_1DxdrwGh0CmXqmnw7x9G2rWd/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "81F7A21-0001",
"object": "invoice",
"paid": true,
"period_end": 1548696360,
"period_start": 1548696360,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "paid",
"subscription": null,
"subtotal": 0,
"tax": 0,
"tax_percent": null,
"total": 0,
"webhooks_delivered_at": 1548696360
}
],
"has_more": false,
"object": "list",
"url": "/v1/invoices"
}

View File

@@ -0,0 +1,22 @@
{
"amount": -64000,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696359,
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwg0R6ha8n",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1548696359,
"start": 1548696359
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"unit_amount": -64000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 64000,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696359,
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdrvGh0CmXqmnwhHM5DcR1",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"unit_amount": 8000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 17442,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696361,
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnwSpeA2crG",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"proration": false,
"quantity": 3,
"subscription": null,
"unit_amount": 5814
}

View File

@@ -0,0 +1,22 @@
{
"amount": 72000,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696361,
"description": "Zulip Standard - renewal",
"discountable": false,
"id": "ii_1DxdrxGh0CmXqmnw183j11lw",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"proration": false,
"quantity": 9,
"subscription": null,
"unit_amount": 8000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 7255,
"currency": "usd",
"customer": "cus_EQUrCdXCP2oqIF",
"date": 1548696362,
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
"discountable": false,
"id": "ii_1DxdryGh0CmXqmnwXSzdImK3",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1388631845,
"start": 1360033445
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"unit_amount": 7255
}

View File

@@ -0,0 +1,33 @@
{
"card": {
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "unchecked",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "unchecked",
"brand": "Visa",
"country": "US",
"cvc_check": "unchecked",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdrtGh0CmXqmnw1LvZ0cL4",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
},
"client_ip": "107.202.144.213",
"created": 1548696357,
"id": "tok_1DxdrtGh0CmXqmnw94EYeDen",
"livemode": false,
"object": "token",
"type": "card",
"used": false
}

View File

@@ -0,0 +1,78 @@
{
"amount": 64000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_1DxdeSGh0CmXqmnw9I7ziL6D",
"captured": true,
"created": 1548695524,
"currency": "usd",
"customer": "cus_EQUd48LphR5ahk",
"description": "Upgrade to Zulip Standard, $80.0 x 8",
"destination": null,
"dispute": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_1DxdeSGh0CmXqmnwsw80Rn8n",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 46,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": null,
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_1BWYgHGh0CmXqmnw/ch_1DxdeSGh0CmXqmnwsw80Rn8n/rcpt_EQUda9O5I5cYpJL0yxiRbxnPbWZ415D",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_1DxdeSGh0CmXqmnwsw80Rn8n/refunds"
},
"review": null,
"shipping": null,
"source": {
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "pass",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "pass",
"brand": "Visa",
"country": "US",
"customer": "cus_EQUd48LphR5ahk",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
},
"source_transfer": null,
"statement_descriptor": "Zulip Standard",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -0,0 +1,65 @@
{
"account_balance": 0,
"created": 1548695523,
"currency": null,
"default_source": "card_1DxdeRGh0CmXqmnwu3BibWtM",
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_EQUd48LphR5ahk",
"invoice_prefix": "9F53235",
"invoice_settings": {
"custom_fields": null,
"footer": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"object": "customer",
"shipping": null,
"sources": {
"data": [
{
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "pass",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "pass",
"brand": "Visa",
"country": "US",
"customer": "cus_EQUd48LphR5ahk",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/customers/cus_EQUd48LphR5ahk/sources"
},
"subscriptions": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/customers/cus_EQUd48LphR5ahk/subscriptions"
},
"tax_info": null,
"tax_info_verification": null
}

View File

@@ -0,0 +1,92 @@
{
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUd48LphR5ahk",
"date": 1548695526,
"default_source": null,
"description": "",
"discount": null,
"due_date": null,
"ending_balance": null,
"finalized_at": null,
"footer": null,
"hosted_invoice_url": null,
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
"invoice_pdf": null,
"lines": {
"data": [
{
"amount": 64000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": -64000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1548695525,
"start": 1548695525
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1548699126,
"number": "9F53235-0001",
"object": "invoice",
"paid": false,
"period_end": 1548695526,
"period_start": 1548695526,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "draft",
"subscription": null,
"subtotal": 0,
"tax": 0,
"tax_percent": null,
"total": 0,
"webhooks_delivered_at": null
}

View File

@@ -0,0 +1,92 @@
{
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"billing": "charge_automatically",
"billing_reason": "manual",
"charge": null,
"currency": "usd",
"custom_fields": null,
"customer": "cus_EQUd48LphR5ahk",
"date": 1548695526,
"default_source": null,
"description": "",
"discount": null,
"due_date": 1551287527,
"ending_balance": 0,
"finalized_at": 1548695527,
"footer": null,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE",
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE/pdf",
"lines": {
"data": [
{
"amount": 64000,
"currency": "usd",
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"type": "invoiceitem"
},
{
"amount": -64000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1548695525,
"start": 1548695525
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "9F53235-0001",
"object": "invoice",
"paid": true,
"period_end": 1548695526,
"period_start": 1548695526,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Standard",
"status": "paid",
"subscription": null,
"subtotal": 0,
"tax": 0,
"tax_percent": null,
"total": 0,
"webhooks_delivered_at": 1548695526
}

View File

@@ -0,0 +1,22 @@
{
"amount": -64000,
"currency": "usd",
"customer": "cus_EQUd48LphR5ahk",
"date": 1548695525,
"description": "Payment (Card ending in 4242)",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1548695525,
"start": 1548695525
},
"plan": null,
"proration": false,
"quantity": 1,
"subscription": null,
"unit_amount": -64000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 64000,
"currency": "usd",
"customer": "cus_EQUd48LphR5ahk",
"date": 1548695525,
"description": "Zulip Standard",
"discountable": false,
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"proration": false,
"quantity": 8,
"subscription": null,
"unit_amount": 8000
}

View File

@@ -0,0 +1,22 @@
{
"amount": 17442,
"currency": "usd",
"customer": "cus_EQUd48LphR5ahk",
"date": 1548695527,
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discountable": false,
"id": "ii_1DxdeVGh0CmXqmnwf6cLlEA7",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"proration": false,
"quantity": 3,
"subscription": null,
"unit_amount": 5814
}

View File

@@ -0,0 +1,33 @@
{
"card": {
"address_city": "Pacific",
"address_country": "United States",
"address_line1": "Under the sea,",
"address_line1_check": "unchecked",
"address_line2": null,
"address_state": null,
"address_zip": "33333",
"address_zip_check": "unchecked",
"brand": "Visa",
"country": "US",
"cvc_check": "unchecked",
"dynamic_last4": null,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "6dAXT9VZvwro65EK",
"funding": "credit",
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
"last4": "4242",
"metadata": {},
"name": "Ada Starr",
"object": "card",
"tokenization_method": null
},
"client_ip": "107.202.144.213",
"created": 1548695523,
"id": "tok_1DxdeRGh0CmXqmnw5X6CeYnC",
"livemode": false,
"object": "token",
"type": "card",
"used": false
}

View File

@@ -30,7 +30,8 @@ from corporate.lib.stripe import catch_stripe_errors, attach_discount_to_realm,
add_months, next_month, next_renewal_date, renewal_amount, \
compute_plan_parameters, update_or_create_stripe_customer, \
process_initial_upgrade, add_plan_renewal_to_license_ledger_if_needed, \
update_license_ledger_if_needed, update_license_ledger_for_automanaged_plan
update_license_ledger_if_needed, update_license_ledger_for_automanaged_plan, \
invoice_plan, invoice_plans_as_needed
from corporate.models import Customer, CustomerPlan, LicenseLedger
from corporate.views import payment_method_string
import corporate.urls
@@ -1027,3 +1028,127 @@ class LicenseLedgerTest(StripeTestCase):
(False, self.seat_count + 1, self.seat_count),
(False, self.seat_count + 1, self.seat_count + 1),
(False, self.seat_count + 1, self.seat_count + 1)])
class InvoiceTest(StripeTestCase):
def test_invoicing_status_is_started(self) -> None:
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
plan = CustomerPlan.objects.first()
plan.invoicing_status = CustomerPlan.STARTED
plan.save(update_fields=['invoicing_status'])
with self.assertRaises(NotImplementedError):
invoice_plan(CustomerPlan.objects.first(), self.now)
@mock_stripe()
def test_invoice_plan(self, *mocks: Mock) -> None:
user = self.example_user("hamlet")
self.login(user.email)
with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
self.upgrade()
# Increase
with patch('corporate.lib.stripe.get_seat_count', return_value=self.seat_count + 3):
update_license_ledger_if_needed(get_realm('zulip'), self.now + timedelta(days=100))
# Decrease
with patch('corporate.lib.stripe.get_seat_count', return_value=self.seat_count):
update_license_ledger_if_needed(get_realm('zulip'), self.now + timedelta(days=200))
# Increase, but not past high watermark
with patch('corporate.lib.stripe.get_seat_count', return_value=self.seat_count + 1):
update_license_ledger_if_needed(get_realm('zulip'), self.now + timedelta(days=300))
# Increase, but after renewal date, and below last year's high watermark
with patch('corporate.lib.stripe.get_seat_count', return_value=self.seat_count + 2):
update_license_ledger_if_needed(get_realm('zulip'), self.now + timedelta(days=400))
# Increase, but after event_time
with patch('corporate.lib.stripe.get_seat_count', return_value=self.seat_count + 3):
update_license_ledger_if_needed(get_realm('zulip'), self.now + timedelta(days=500))
plan = CustomerPlan.objects.first()
invoice_plan(plan, self.now + timedelta(days=400))
stripe_invoices = [invoice for invoice in stripe.Invoice.list(
customer=plan.customer.stripe_customer_id)]
self.assertEqual(len(stripe_invoices), 2)
self.assertIsNotNone(stripe_invoices[0].finalized_at)
stripe_line_items = [item for item in stripe_invoices[0].lines]
self.assertEqual(len(stripe_line_items), 3)
line_item_params = {
'amount': int(8000 * (1 - ((400-366) / 365)) + .5),
'description': 'Additional license (Feb 5, 2013 - Jan 2, 2014)',
'discountable': False,
'period': {
'start': datetime_to_timestamp(self.now + timedelta(days=400)),
'end': datetime_to_timestamp(self.now + timedelta(days=2*365 + 1))},
'quantity': 1}
for key, value in line_item_params.items():
self.assertEqual(stripe_line_items[0].get(key), value)
line_item_params = {
'amount': 8000 * (self.seat_count + 1),
'description': 'Zulip Standard - renewal',
'discountable': False,
'period': {
'start': datetime_to_timestamp(self.now + timedelta(days=366)),
'end': datetime_to_timestamp(self.now + timedelta(days=2*365 + 1))},
'quantity': (self.seat_count + 1)}
for key, value in line_item_params.items():
self.assertEqual(stripe_line_items[1].get(key), value)
line_item_params = {
'amount': 3 * int(8000 * (366-100) / 366 + .5),
'description': 'Additional license (Apr 11, 2012 - Jan 2, 2013)',
'discountable': False,
'period': {
'start': datetime_to_timestamp(self.now + timedelta(days=100)),
'end': datetime_to_timestamp(self.now + timedelta(days=366))},
'quantity': 3}
for key, value in line_item_params.items():
self.assertEqual(stripe_line_items[2].get(key), value)
@mock_stripe()
def test_fixed_price_plans(self, *mocks: Mock) -> None:
# Also tests charge_automatically=False
user = self.example_user("hamlet")
self.login(user.email)
with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
self.upgrade(invoice=True)
plan = CustomerPlan.objects.first()
plan.fixed_price = 100
plan.price_per_license = 0
plan.save(update_fields=['fixed_price', 'price_per_license'])
invoice_plan(plan, self.next_year)
stripe_invoices = [invoice for invoice in stripe.Invoice.list(
customer=plan.customer.stripe_customer_id)]
self.assertEqual(len(stripe_invoices), 2)
self.assertEqual(stripe_invoices[0].billing, 'send_invoice')
stripe_line_items = [item for item in stripe_invoices[0].lines]
self.assertEqual(len(stripe_line_items), 1)
line_item_params = {
'amount': 100,
'description': 'Zulip Standard - renewal',
'discountable': False,
'period': {
'start': datetime_to_timestamp(self.next_year),
'end': datetime_to_timestamp(self.next_year + timedelta(days=365))},
'quantity': 1}
for key, value in line_item_params.items():
self.assertEqual(stripe_line_items[0].get(key), value)
def test_no_invoice_needed(self) -> None:
with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
plan = CustomerPlan.objects.first()
self.assertEqual(plan.next_invoice_date, self.next_month)
# Test this doesn't make any calls to stripe.Invoice or stripe.InvoiceItem
invoice_plan(plan, self.next_month)
plan = CustomerPlan.objects.first()
# Test that we still update next_invoice_date
self.assertEqual(plan.next_invoice_date, self.next_month + timedelta(days=29))
def test_invoice_plans_as_needed(self) -> None:
with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
plan = CustomerPlan.objects.first()
self.assertEqual(plan.next_invoice_date, self.next_month)
# Test nothing needed to be done
with patch('corporate.lib.stripe.invoice_plan') as mocked:
invoice_plans_as_needed(self.next_month - timedelta(days=1))
mocked.assert_not_called()
# Test something needing to be done
invoice_plans_as_needed(self.next_month)
plan = CustomerPlan.objects.first()
self.assertEqual(plan.next_invoice_date, self.next_month + timedelta(days=29))

View File

@@ -173,7 +173,7 @@ class InvoiceItem:
@staticmethod
def create(amount: int=..., currency: str=..., customer: str=..., description: str=...,
discountable: bool=..., period: Dict[str, int]=..., quantity: int=...,
unit_amount: int=...) -> InvoiceItem:
unit_amount: int=..., idempotency_key: str=...) -> InvoiceItem:
...
@staticmethod