Stripe Invoices: Automated Billing and Payment Collection
In this tutorial, you will learn about Stripe Invoices: Automated Billing and Payment Collection. We cover key concepts, practical examples, and best practices to help you master this topic.
Stripe Invoices represent a request for payment, automatically generated for subscriptions, supporting one-time charges, payment retries, discounts, tax calculation, and hosted invoice pages.
What You'll Learn
How invoices are generated for subscriptions, how to create one-time invoices, manage auto-advance and collection, configure payment retry logic, add discounts and tax, and use the hosted invoice page.
Why It Matters
Automated invoicing ensures reliable revenue collection, reduces payment failures through smart retries, and provides professional billing documents. DodaTech processes thousands of subscription invoices monthly through Stripe's automated billing engine.
Real-World Use
A Pro customer's subscription renews. Stripe generates an invoice, sends it via email, attempts payment with the saved card, retries on failure, and marks it paid or finalizes after dunning.
flowchart LR
A["Subscription\nRenewal Date"] --> B["Generate\nInvoice"]
B --> C["Auto-Advance\n= True"]
C --> D["Attempt\nPayment"]
D --> E{"Payment\nResult"}
E -->|Success| F["Invoice\nPaid"]
E -->|Failure| G["Retry\n(4 attempts)"]
G --> H{"All\nFailed"}
H -->|Yes| I["Invoice\nUncollectible"]
H -->|No| D
style A fill:#6772e5,color:#fff
style D fill:#dbeafe,stroke:#2563eb
style F fill:#bbf7d0,stroke:#16a34a
style I fill:#fecaca,stroke:#dc2626
Viewing and Finalizing Invoices
import stripe
stripe.api_key = "sk_test_..."
# Retrieve invoices for a subscription
subscription_id = "sub_ProABC123"
invoices = stripe.Invoice.list(subscription=subscription_id, limit=5)
for inv in invoices:
print(f"Invoice: {inv.id}")
print(f" Amount Due: ${inv.amount_due/100:.2f}")
print(f" Amount Paid: ${inv.amount_paid/100:.2f}")
print(f" Status: {inv.status}")
print(f" Created: {inv.created}")
print()
# Expected output:
# Invoice: in_1JanInvoiceABC
# Amount Due: $29.99
# Amount Paid: $29.99
# Status: paid
# Created: 1719561600
# Finalize a draft invoice
def finalize_invoice(invoice_id):
invoice = stripe.Invoice.finalize_invoice(invoice_id)
print(f"Finalized: {invoice.id}")
print(f"Status: {invoice.status}")
print(f"Hosted URL: {invoice.hosted_invoice_url}")
return invoice
# Invoice: in_1DraftInvoiceXYZ
# Finalized: in_1DraftInvoiceXYZ
# Status: open
# Hosted URL: https://invoice.stripe.com/i/acct_...
One-Time Invoices
def create_one_time_invoice(customer_id, items, days_until_due=30):
# Create invoice items first
for item in items:
stripe.InvoiceItem.create(
customer=customer_id,
amount=item["amount"],
currency=item.get("currency", "usd"),
description=item["description"],
quantity=item.get("quantity", 1)
)
invoice = stripe.Invoice.create(
customer=customer_id,
collection_method="send_invoice",
days_until_due=days_until_due
)
invoice = stripe.Invoice.finalize_invoice(invoice.id)
print(f"Invoice: {invoice.id}")
print(f"Amount Due: ${invoice.amount_due/100:.2f}")
print(f"Payment URL: {invoice.hosted_invoice_url}")
return invoice
# Usage
items = [
{"amount": 5000, "description": "Consulting Hourly", "quantity": 10},
{"amount": 2000, "description": "Setup Fee", "quantity": 1}
]
# cus_CustomerXYZ is an existing customer
inv = create_one_time_invoice("cus_CustomerXYZ", items)
# Expected output:
# Invoice: in_1OneTimeABC
# Amount Due: $520.00
# Payment URL: https://invoice.stripe.com/i/acct_...
Payment Retries and Dunning
# Configure payment settings for automatic retries
def configure_payment_settings(customer_id, payment_method_id):
# Set default payment method
stripe.Customer.update(
customer_id,
invoice_settings={
"default_payment_method": payment_method_id
}
)
# Stripe automatically retries failed invoices
# Default: 4 retries over ~30 days
# You can customize retry behavior via Dashboard
print(f"Default retry configured for {customer_id}")
# Manually trigger a payment attempt
def retry_invoice_payment(invoice_id):
invoice = stripe.Invoice.pay(invoice_id)
print(f"Retry result: {invoice.id} ({invoice.status})")
if invoice.status == "paid":
print("Payment succeeded on retry")
else:
print(f"Still failed. Last error: {invoice.last_finalization_error}")
return invoice
# Expected output:
# Retry result: in_1FailedRetryABC (paid)
# Payment succeeded on retry
Common Mistakes
1. Not Handling auto_advance Correctly
If auto_advance is false, the invoice stays as draft forever. It won't be finalized or sent. Set auto_advance: true for automatic processing.
2. Forgetting to Finalize Draft Invoices
Draft invoices do not attempt payment. You must call finalize_invoice to transition from draft to open, which triggers payment collection.
3. Using send_invoice Without days_until_due
When collection_method: send_invoice, you must set days_until_due. Without it, Stripe defaults to 0, making it due immediately and potentially causing failed collection.
4. Ignoring Failed Payment Retries
Stripe retries failed payments up to 4 times. Monitor payment_intent.status on invoices. If all retries fail, the invoice becomes uncollectible. Notify customers before the final attempt.
5. Creating Duplicate Line Items
Calling InvoiceItem.create multiple times creates separate line items. If you want a single line with quantity, pass quantity in the invoice item rather than creating duplicates.
Practice Questions
- What is the lifecycle of a subscription invoice?
- How do you create a one-time invoice for a custom amount?
- What happens when an invoice payment fails?
- How do customers pay a
send_invoicetype invoice?
Answers:
draft->open(after finalize) ->paidoruncollectibleorvoid. Subscriptions auto-generate invoices at renewal, which auto-advance through the lifecycle.- Create InvoiceItems for each line with
InvoiceItem.create, then create an Invoice withcollection_method: send_invoice, then callfinalize_invoice. - Stripe automatically retries up to 4 times over ~30 days. You can also manually retry via API. After all retries fail, the invoice becomes
uncollectible. - Customers receive an email with a link to Stripe's hosted invoice page where they can pay by card or bank transfer. The URL is available via
invoice.hosted_invoice_url.
Challenge: Build an invoicing system: create a subscription for a customer, let it generate an invoice, retrieve and display invoice details, simulate a payment failure by using a failing test card (4000000000000002), configure automatic retry, then manually trigger payment after updating the card to a valid test card.
FAQ
Mini Project
Build an invoice management system: create a subscription, retrieve the generated invoice, add a one-time credit note via InvoiceItem, finalize, attempt payment with a failing card, observe the dunning cycle, update the payment method, retry successfully, and verify the paid invoice status.
What's Next
Testing Stripe — simulate various payment scenarios using test cards.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro