Skip to content

feat(stripe): subscriptions and recurring reconciliation#241

Open
Tarunkumar0601 wants to merge 6 commits into
frappe:developfrom
aerele:feat/stripe-subscriptions
Open

feat(stripe): subscriptions and recurring reconciliation#241
Tarunkumar0601 wants to merge 6 commits into
frappe:developfrom
aerele:feat/stripe-subscriptions

Conversation

@Tarunkumar0601

@Tarunkumar0601 Tarunkumar0601 commented Jun 29, 2026

Copy link
Copy Markdown

Adds native recurring billing through Stripe and reconciles each renewal back to ERPNext.

  • Subscription Payment Requests go through Hosted Checkout in subscription mode
  • Two billing models: Bill From Cycle One and Charge Now + Defer First Cycle
  • Syncs each Subscription Plan to a Stripe Price; links the ERPNext Subscription to the Stripe subscription
  • invoice.paid → finds/generates the period Sales Invoice and books a Payment Entry
  • Handles invoice.payment_failed (dunning) and customer.subscription.updated/deleted (status sync)

Depends on feat/stripe-webhooks-reconciliation.

Backport Needed: V15 & V16

@Tarunkumar0601 Tarunkumar0601 marked this pull request as ready for review July 1, 2026 10:35
@mergify

mergify Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The webhook reconciliation path is now correct for the happy path, but the SI-generation inside _generate_and_find_sales_invoice calls sub_doc.process(), which commits internally in ERPNext — meaning a subsequent _create_payment_entry failure cannot roll back the generated Sales Invoice despite the comment claiming otherwise.

All 20 previously-flagged concerns appear addressed. One residual concern: sub_doc.process() is ERPNext-internal and performs its own frappe.db.commit(), so the 'no commit here' comment in _generate_and_find_sales_invoice is incorrect — a PE failure after it will leave a committed, orphaned SI with no Payment Entry until Stripe retries.

payments/payment_gateways/stripe_reconcile.py — specifically _generate_and_find_sales_invoice and the transactional guarantee it claims around sub_doc.process().

Reviews (27): Last reviewed commit: "feat(stripe): subscriptions and recurrin..." | Re-trigger Greptile

Comment thread payments/payment_gateways/stripe_integration.py Outdated
Comment thread payments/payment_gateways/stripe_subscription_sync.py Outdated
Comment thread payments/payment_gateways/stripe_reconcile.py
Comment thread payments/payment_gateways/stripe_reconcile.py
Comment thread payments/payment_gateways/stripe_utils.py
Comment thread payments/payment_gateways/stripe_subscription_sync.py
Comment thread payments/payment_gateways/doctype/stripe_settings/__init__.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch from 77847da to 4368ab6 Compare July 1, 2026 14:10
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch from 4368ab6 to e51884f Compare July 1, 2026 14:51
Comment thread payments/payment_gateways/doctype/stripe_settings/stripe_settings.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 85239d2 to 9e67140 Compare July 1, 2026 17:02
Comment thread payments/payment_gateways/stripe_utils.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 4ab6529 to cbd3e7a Compare July 2, 2026 06:20
Comment thread payments/payment_gateways/stripe_utils.py
Comment thread payments/templates/pages/stripe_checkout.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from a0f69f7 to 9072b7c Compare July 2, 2026 07:16
Comment thread payments/payment_gateways/doctype/stripe_settings/stripe_settings.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 3cc0e28 to 84e49e2 Compare July 2, 2026 11:55
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 9353348 to 7a1bcf2 Compare July 2, 2026 13:06
Comment thread payments/payment_gateways/stripe_subscription_sync.py Outdated
Comment thread payments/payment_gateways/stripe_reconcile.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch from 7a1bcf2 to 5c22513 Compare July 3, 2026 05:44
Comment thread payments/payment_gateways/stripe_reconcile.py
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 3 times, most recently from 0a0c9c4 to ab7b2da Compare July 3, 2026 06:44
Comment thread payments/payment_gateways/stripe_reconcile.py Outdated
Comment thread payments/payment_gateways/stripe_integration.py
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch from ab7b2da to 37847e0 Compare July 3, 2026 07:36
Comment thread payments/payment_gateways/stripe_utils.py
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 879f0b1 to 90ab96b Compare July 3, 2026 08:25
Comment thread payments/payment_gateways/stripe_reconcile.py Outdated
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 2 times, most recently from 169f6fa to 57fb077 Compare July 3, 2026 09:27
Comment thread payments/payment_gateways/stripe_reconcile.py
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch 4 times, most recently from 9a8f25b to 4c915e4 Compare July 3, 2026 10:43
@Tarunkumar0601 Tarunkumar0601 force-pushed the feat/stripe-subscriptions branch from 4c915e4 to 848df01 Compare July 3, 2026 10:56
Comment on lines +193 to +204
def _generate_and_find_sales_invoice(erpnext_sub, invoice):
"""Stripe billed before ERPNext's scheduler — generate the period SI, then re-find."""
_, end = _invoice_period(invoice)
try:
sub_doc = frappe.get_doc("Subscription", erpnext_sub)
# No commit here: keep SI generation in the webhook's transaction so a later
# Payment Entry failure rolls back the SI too (handle_event retries the event).
sub_doc.process(posting_date=end or getdate(now_datetime()))
except Exception:
frappe.log_error(frappe.get_traceback(), "Stripe: could not generate subscription invoice")
return None
return _find_period_sales_invoice(erpnext_sub, invoice)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 ERPNext sub_doc.process() commits internally — orphaned SI on PE failure

The comment says "No commit here: keep SI generation in the webhook's transaction so a later Payment Entry failure rolls back the SI too." ERPNext's Subscription.process() calls frappe.db.commit() internally after generating each invoice, so the SI is committed to the database before _create_payment_entry is called. If _create_payment_entry then raises (e.g., validation error), handle_event rolls back — but the SI commit is already durable. The result is a submitted SI with no Payment Entry, identical to the issue that was fixed by removing the earlier explicit frappe.db.commit(). The retry will find _find_period_sales_invoice returning the committed SI and can proceed to create the PE, so it is eventually consistent — but the claim in the comment that the SI is inside the webhook transaction is incorrect and may mislead future contributors into assuming rollback protection that does not exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant