Skip to content

Commit

Permalink
Merge pull request #4408 from magfest/self-service-refund-fees
Browse files Browse the repository at this point in the history
Enable self-service refunds excluding fees
  • Loading branch information
kitsuta authored Oct 8, 2024
2 parents 6aa407a + 3a41602 commit e02b5ac
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 29 deletions.
6 changes: 6 additions & 0 deletions uber/configspec.ini
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@ self_service_deferrals_open = boolean(default=False)
# and asked to pay this fee.
merch_shipping_fee = integer(default=15)

# If true, and we are NOT using Authorize.net, self-service refunds will exclude processing fees.
# Requires refund_cutoff to be set to enable self-service refunds.
# Admins can always choose to refund with or without fees when cancelling a receipt.
exclude_fees_from_refunds = boolean(default=True)

# This URL, if set, will show up alongside or without the above extra donation field
extra_donation_url = string(default="")

Expand Down Expand Up @@ -1134,6 +1139,7 @@ refund_start = string(default="")

# Before this date, attendees with single paid badges may self-service refund their badge.
# Leave this blank to disable self-service refunds.
# See exclude_fees_from_refunds for setting whether these refunds include processing fees.
refund_cutoff = string(default="")

# All parts of the site go offline after this date (which is also used in several emails).
Expand Down
67 changes: 44 additions & 23 deletions uber/site_sections/preregistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
from uber.errors import HTTPRedirect
from uber.forms import load_forms
from uber.models import Attendee, AttendeeAccount, Attraction, Email, Group, PromoCode, PromoCodeGroup, \
ReceiptTransaction, Tracking
ModelReceipt, ReceiptItem, ReceiptTransaction, Tracking
from uber.tasks.email import send_email
from uber.utils import add_opt, check, localized_now, normalize_email, normalize_email_legacy, genpasswd, valid_email, \
valid_password, SignNowRequest, validate_model, create_new_hash, get_age_conf_from_birthday
from uber.payments import PreregCart, TransactionRequest, ReceiptManager
from uber.payments import PreregCart, TransactionRequest, ReceiptManager, SpinTerminalRequest


def check_if_can_reg(is_dealer_reg=False):
Expand Down Expand Up @@ -1685,41 +1685,68 @@ def not_found(self, id, message=''):
def abandon_badge(self, session, id):
from uber.custom_tags import format_currency
attendee = session.attendee(id)
page_redirect = ''
if attendee.amount_paid and not attendee.is_group_leader:
failure_message = "Something went wrong with your refund. Please contact us at {}."\
.format(email_only(c.REGDESK_EMAIL))
page_redirect = 'repurchase'
else:
success_message = "Your badge has been successfully cancelled. Sorry you can't make it! We hope to see you next year!"
page_redirect = '../landing/index'
if attendee.is_group_leader:
failure_message = "You cannot abandon your badge because you are the leader of a group."
else:
failure_message = "You cannot abandon your badge for some reason. Please contact us at {}."\
.format(email_only(c.REGDESK_EMAIL))
page_redirect = 'homepage' if c.ATTENDEE_ACCOUNTS_ENABLED else page_redirect
page_redirect = 'homepage' if c.ATTENDEE_ACCOUNTS_ENABLED else '../landing/index'

if (not attendee.amount_paid and attendee.cannot_abandon_badge_reason)\
or (attendee.amount_paid and attendee.cannot_self_service_refund_reason):
raise HTTPRedirect('confirm?id={}&message={}', id, failure_message)

if attendee.amount_paid:
receipt = session.get_receipt_by_model(attendee)
total_refunded = 0
for txn in receipt.receipt_txns:
refund = TransactionRequest(receipt, amount=txn.amount_left, who='non-admin')
error = refund.refund_or_skip(txn)
if error:
raise HTTPRedirect('confirm?id={}&message={}', id, error)
session.add_all(refund.get_receipt_items_to_add())
total_refunded += refund.amount
refund_total = 0
session.add(ReceiptItem(
receipt_id=receipt.id,
department=c.REG_RECEIPT_ITEM,
category=c.CANCEL_ITEM,
desc=f"Refunding and Cancelling {attendee.full_name}'s Badge",
amount=-(receipt.payment_total - receipt.refund_total),
who='non-admin',
))
session.commit()
session.refresh(receipt)
for txn in receipt.refundable_txns:
if txn.department == getattr(attendee, 'department', c.OTHER_RECEIPT_ITEM):
refund_amount = txn.amount_left
if not c.AUTHORIZENET_LOGIN_ID and c.EXCLUDE_FEES_FROM_REFUNDS:
processing_fees = txn.calc_processing_fee(refund_amount)
session.add(ReceiptItem(
receipt_id=txn.receipt.id,
department=c.OTHER_RECEIPT_ITEM,
category=c.PROCESSING_FEES,
desc=f"Processing Fees for Full Refund of {txn.desc}",
amount=processing_fees,
who='non-admin',
))
refund_amount -= processing_fees
session.commit()
session.refresh(receipt)

if txn.method == c.SQUARE and c.SPIN_TERMINAL_AUTH_KEY:
refund = SpinTerminalRequest(receipt=receipt, amount=refund_amount, method=txn.method)
else:
refund = TransactionRequest(receipt=receipt, amount=refund_amount, method=txn.method)

error = refund.refund_or_skip(txn)
if error:
raise HTTPRedirect('confirm?id={}&message={}', id, error)
session.add_all(refund.get_receipt_items_to_add())
refund_total += refund.amount

receipt.closed = datetime.now()
session.add(receipt)

success_message = "Your badge has been successfully cancelled. Your refund of {} should appear on your credit card in 7-10 days."\
.format(format_currency(total_refunded / 100))
.format(format_currency(refund_total / 100))
if attendee.paid == c.HAS_PAID:
attendee.paid = c.REFUNDED

Expand All @@ -1734,19 +1761,13 @@ def abandon_badge(self, session, id):
paid=attendee.paid)

session.delete_from_group(attendee, attendee.group)
if page_redirect != 'homepage':
raise HTTPRedirect('{}?id={}&message={}', page_redirect, attendee.id, success_message)
else:
raise HTTPRedirect('{}?message={}', page_redirect, success_message)
raise HTTPRedirect('{}?message={}', page_redirect, success_message)
# otherwise, we will mark attendee as invalid and remove them from shifts if necessary
else:
attendee.badge_status = c.REFUNDED_STATUS
for shift in attendee.shifts:
session.delete(shift)
if page_redirect != 'homepage':
raise HTTPRedirect('{}?id={}&message={}', page_redirect, attendee.id, success_message)
else:
raise HTTPRedirect('{}?message={}', page_redirect, success_message)
raise HTTPRedirect('{}?message={}', page_redirect, success_message)

def badge_updated(self, session, id, message=''):
return {
Expand Down
10 changes: 4 additions & 6 deletions uber/site_sections/reg_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,6 @@ def settle_up(self, session, id=''):
def process_full_refund(self, session, id='', attendee_id='', group_id='', exclude_fees=False):
receipt = session.model_receipt(id)
refund_total = 0
processing_fee_total = 0
group_leader_receipt = None
group_refund_amount = 0

Expand All @@ -603,7 +602,7 @@ def process_full_refund(self, session, id='', attendee_id='', group_id='', exclu
model = session.group(group_id)

if session.get_receipt_by_model(model) == receipt:
refund_desc = "Full Refund for {model.id}"
refund_desc = f"Full Refund for {model.id}"
if isinstance(model, Attendee):
refund_desc = f"Refunding and Cancelling {model.full_name}'s Badge",
elif isinstance(model, Group):
Expand All @@ -614,7 +613,7 @@ def process_full_refund(self, session, id='', attendee_id='', group_id='', exclu
department=receipt.default_department,
category=c.CANCEL_ITEM,
desc=refund_desc,
amount=-(refund_total + processing_fee_total),
amount=-(receipt.payment_total - receipt.refund_total),
who=AdminAccount.admin_name() or 'non-admin',
))
session.commit()
Expand All @@ -624,7 +623,7 @@ def process_full_refund(self, session, id='', attendee_id='', group_id='', exclu
if txn.department == getattr(model, 'department', c.OTHER_RECEIPT_ITEM):
refund_amount = txn.amount_left
if exclude_fees:
processing_fees = txn.calc_processing_fee(txn.amount_left)
processing_fees = txn.calc_processing_fee(refund_amount)
session.add(ReceiptItem(
receipt_id=txn.receipt.id,
department=c.OTHER_RECEIPT_ITEM,
Expand All @@ -634,7 +633,6 @@ def process_full_refund(self, session, id='', attendee_id='', group_id='', exclu
who=AdminAccount.admin_name() or 'non-admin',
))
refund_amount -= processing_fees
processing_fee_total += processing_fees
session.commit()
session.refresh(receipt)

Expand Down Expand Up @@ -678,7 +676,7 @@ def process_full_refund(self, session, id='', attendee_id='', group_id='', exclu

session.add(ReceiptItem(
receipt_id=txn.receipt.id,
department=department,
department=c.REG_RECEIPT_ITEM,
category=c.REFUND,
desc=f"Refunding {model.full_name}'s Promo Code",
amount=-group_refund_amount,
Expand Down

0 comments on commit e02b5ac

Please sign in to comment.