Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions buzz/ticketing/doctype/event_payment/event_payment.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,58 @@
// Copyright (c) 2025, BWH Studios and contributors
// For license information, please see license.txt

// frappe.ui.form.on("Event Payment", {
// refresh(frm) {
frappe.ui.form.on("Event Payment", {
refresh(frm) {
if (
frm.doc.payment_gateway === "Razorpay" &&
frm.doc.payment_received &&
frm.doc.refund_status !== "Refunded"
) {
frm.add_custom_button(__("Refund"), () => show_refund_dialog(frm));
}

// },
// });
if (frm.doc.refund_status === "Refund Pending") {
frm.add_custom_button(__("Sync Refund Status"), async () => {
const status = await frm.call("sync_refund_status");
frappe.show_alert({
message: __("Refund Status: {0}", [status.message]),
indicator: "green",
});
frm.reload_doc();
});
}
},
});

function show_refund_dialog(frm) {
const refundable_amount = frm.doc.amount - frm.doc.refunded_amount;

const dialog = new frappe.ui.Dialog({
title: __("Refund Payment"),
fields: [
{
fieldname: "amount",
fieldtype: "Currency",
label: __("Refund Amount"),
options: frm.doc.currency,
default: refundable_amount,
reqd: 1,
description: __("Maximum refundable: {0}", [
format_currency(refundable_amount, frm.doc.currency),
]),
},
],
primary_action_label: __("Refund"),
async primary_action({ amount }) {
dialog.hide();
const status = await frm.call("refund", { amount });
frappe.show_alert({
message: __("Refund Status: {0}", [status.message]),
indicator: "green",
});
frm.reload_doc();
},
});

dialog.show();
}
38 changes: 37 additions & 1 deletion buzz/ticketing/doctype/event_payment/event_payment.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
"payment_gateway",
"column_break_oauu",
"payment_id",
"order_id"
"order_id",
"refund_section",
"refund_status",
"refund_id",
"column_break_refund",
"refunded_amount"
],
"fields": [
{
Expand Down Expand Up @@ -88,6 +93,37 @@
"fieldtype": "Link",
"label": "Payment Gateway",
"options": "Payment Gateway"
},
{
"depends_on": "refund_status",
"fieldname": "refund_section",
"fieldtype": "Section Break",
"label": "Refund"
},
{
"fieldname": "refund_status",
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Refund Status",
"options": "\nRefund Pending\nPartially Refunded\nRefunded",
"read_only": 1
},
{
"fieldname": "refund_id",
"fieldtype": "Data",
"label": "Refund ID",
"read_only": 1
},
{
"fieldname": "column_break_refund",
"fieldtype": "Column Break"
},
{
"fieldname": "refunded_amount",
"fieldtype": "Currency",
"label": "Refunded Amount",
"options": "currency",
"read_only": 1
}
],
"grid_page_length": 50,
Expand Down
54 changes: 52 additions & 2 deletions buzz/ticketing/doctype/event_payment/event_payment.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Copyright (c) 2025, BWH Studios and contributors
# For license information, please see license.txt

# import frappe
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import flt

from buzz.payments import get_controller


class EventPayment(Document):
Expand All @@ -23,7 +27,53 @@ class EventPayment(Document):
payment_received: DF.Check
reference_docname: DF.DynamicLink | None
reference_doctype: DF.Link | None
refund_id: DF.Data | None
refund_status: DF.Literal["", "Refund Pending", "Partially Refunded", "Refunded"]
refunded_amount: DF.Currency
user: DF.Link
# end: auto-generated types

pass
@frappe.whitelist()
def refund(self, amount: float | None = None):
frappe.only_for("System Manager")

if not self.payment_received or not self.payment_id:
frappe.throw(_("Only received payments can be refunded"))

if self.refund_status == "Refunded":
frappe.throw(_("This payment has already been fully refunded"))

controller = get_controller(self.payment_gateway)
if not hasattr(controller, "refund_payment"):
frappe.throw(_("Refunds are not supported for {0}").format(self.payment_gateway))

refund = controller.refund_payment(self.payment_id, amount)

self.refund_id = refund.get("id")
self.refunded_amount = flt(self.refunded_amount) + flt(refund.get("amount")) / 100
self.set_refund_status(refund.get("status"))
self.save()

return self.refund_status

@frappe.whitelist()
def sync_refund_status(self):
frappe.only_for("System Manager")

if not self.refund_id:
frappe.throw(_("No refund found on this payment"))

controller = get_controller(self.payment_gateway)
refund = controller.fetch_refund(self.refund_id)
self.set_refund_status(refund.get("status"))
self.save()

return self.refund_status

def set_refund_status(self, gateway_refund_status: str):
if gateway_refund_status != "processed":
self.refund_status = "Refund Pending"
elif flt(self.refunded_amount) >= flt(self.amount):
self.refund_status = "Refunded"
else:
self.refund_status = "Partially Refunded"
Loading