Skip to content

Payment Request lacks on_payment_authorized() handler for Stripe success flow, causing successful Stripe payments to remain in Requested state #204

Description

@SYSINFRACH

Summary

A successful Stripe payment against Payment Request can complete on Stripe, create a completed Integration Request, and still leave the ERPNext Payment Request in status Requested.

No Payment Entry is created automatically, even though the payment is already captured successfully by Stripe.

The underlying problem is that the Stripe integration calls:

frappe.get_doc(reference_doctype, reference_docname).run_method("on_payment_authorized", status)

but ERPNext's Payment Request doctype does not implement on_payment_authorized().

As a result, the payment can succeed externally while the ERPNext document is not finalized internally.

Affected components

ERPNext file:

erpnext/accounts/doctype/payment_request/payment_request.py

Interaction initiated from:

payments/payment_gateways/doctype/stripe_settings/stripe_settings.py

Environment

Please replace this section with your exact output from:

erpnext 16.9.1 version-16 (99a81db)
frappe 16.11.0 version-16 (694a53f)
payments 0.0.1 develop (3cebd94)
print_designer 1.6.7 develop (9d62227)
swiss_accounting_software 0.0.6 main (5388f1c)

Observed in our environment with:

  • ERPNext Payment Request
  • Stripe via official payments app
  • Stripe test mode
  • Payment Request against Sales Invoice

Steps to reproduce

  1. Configure Stripe with the official payments app.
  2. Create a Payment Request against a Sales Invoice.
  3. Open the generated payment URL and complete the Stripe payment successfully.
  4. Check Stripe and ERPNext afterwards.

Actual behavior

Observed result:

  • Stripe charge succeeds
  • Stripe sends receipt
  • Integration Request is marked Completed
  • Payment Request remains Requested
  • no automatic Payment Entry is created
  • the underlying invoice remains effectively unpaid on the ERPNext side

In our case, the Integration Request looked correct:

  • Service: Stripe
  • Status: Completed
  • No explicit error in the integration request itself

However, the ERPNext Payment Request did not transition to Paid.

Expected behavior

When Stripe payment succeeds for a Payment Request, ERPNext should:

  1. finalize the Payment Request
  2. create / submit the corresponding Payment Entry
  3. update Payment Request.status to Paid
  4. reduce / clear the outstanding amount appropriately
  5. update the linked reference document state as expected

Root cause

The Stripe integration calls:

frappe.get_doc(
    self.data.reference_doctype,
    self.data.reference_docname
).run_method("on_payment_authorized", self.flags.status_changed_to)

For a Stripe payment against Payment Request, the reference document is:

  • reference_doctype = "Payment Request"
  • reference_docname = <payment request id>

But ERPNext's PaymentRequest class does not define:

def on_payment_authorized(self, payment_status=None):
    ...

We verified this directly in bench console:

doc = frappe.get_doc("Payment Request", "ACC-PRQ-2026-00002")
hasattr(doc, "on_payment_authorized")
# False

At the same time, PaymentRequest already contains the correct business logic that should be used on successful payment:

  • set_as_paid()
  • create_payment_entry()

So the finalization logic exists, but the callback expected by the payment gateway integration does not.

Proposed fix

Add an on_payment_authorized() handler to PaymentRequest that bridges successful payment authorization to the existing ERPNext logic.

Suggested implementation:

def on_payment_authorized(self, payment_status=None):
        if payment_status == "Completed":
                if self.status != "Paid":
                        return self.set_as_paid()
        elif payment_status in ("Failed", "Cancelled"):
                self.set_failed()

There is also a related improvement opportunity because set_failed() is currently a no-op in our observed code:

def set_failed(self):
        pass

A minimal improvement would be:

def set_failed(self):
        self.db_set("status", "Failed")

Why this matters

This is a serious accounting / operational bug.

The external payment succeeds, but ERPNext does not complete the internal accounting flow automatically. This creates a mismatch between:

  • payment provider state
  • integration request state
  • ERPNext accounting state

In practice, this means users can receive money successfully in Stripe while the ERPNext Payment Request remains open.

Local verification

We verified locally that adding:

def on_payment_authorized(self, payment_status=None):
        if payment_status == "Completed":
                if self.status != "Paid":
                        return self.set_as_paid()
        elif payment_status in ("Failed", "Cancelled"):
                self.set_failed()

to erpnext/accounts/doctype/payment_request/payment_request.py

caused the previously failing Stripe completion flow to mark the Payment Request as paid successfully.

Additional context

This issue is triggered by the Stripe flow in the payments app, but the missing callback is on ERPNext's Payment Request doctype itself. Because of that, this issue likely needs coordination between erpnext and payments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions