Skip to content

Commit

Permalink
Implement various fixes for receipts
Browse files Browse the repository at this point in the history
Removes the refund transaction button to fix https://jira.magfest.net/browse/MAGDEV-1248. Also to facilitate this, refreshing a transaction from stripe will automatically add a refund item to the receipt. Also also, fixes it so that upgrades that attendees buy during prereg can be reverted, and adds several fixes to actually refunding and reverting or comping items.
  • Loading branch information
kitsuta committed Jul 7, 2023
1 parent f5fb779 commit 53b0561
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 52 deletions.
2 changes: 1 addition & 1 deletion uber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@ def preprocess_refund(self, txn, amount=0, already_refunded_error=True):
if not charge_id:
return "We could not find record of this payment being completed."

already_refunded = txn.update_amount_refunded()
already_refunded, last_refund_id = txn.update_amount_refunded()
if txn.amount - already_refunded <= 0:
if already_refunded_error:
return "This payment has already been fully refunded."
Expand Down
5 changes: 4 additions & 1 deletion uber/models/commerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,16 +341,19 @@ def update_amount_refunded(self):
if not self.intent_id:
return 0

last_refund_id = None
refunded_total = 0
for refund in stripe.Refund.list(payment_intent=self.intent_id):
refunded_total += refund.amount
last_refund_id = refund.id
self.refund_id = self.refund_id or last_refund_id
with Session() as session:
other_txns = session.query(ReceiptTransaction).filter_by(intent_id=self.intent_id
).filter(ReceiptTransaction.id != self.id)
other_refunds = sum([txn.refunded for txn in other_txns])

self.refunded = min(self.amount, refunded_total - other_refunds)
return self.refunded
return self.refunded, last_refund_id

@property
def cannot_delete_reason(self):
Expand Down
40 changes: 21 additions & 19 deletions uber/receipt_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@cost_calculation.MarketplaceApplication
def app_cost(app):
if app.status == c.APPROVED:
return ("Marketplace Application Fee", app.overridden_price * 100 or c.MARKETPLACE_FEE * 100 or 0)
return ("Marketplace Application Fee", app.overridden_price * 100 or c.MARKETPLACE_FEE * 100 or 0, None)


ArtShowApplication.cost_changes = {
Expand All @@ -28,27 +28,27 @@ def app_cost(app):
@cost_calculation.ArtShowApplication
def overridden_app_cost(app):
if app.status == c.APPROVED and app.overridden_price != None:
return ("Art Show Application (Custom Price)", app.overridden_price * 100)
return ("Art Show Application (Custom Price)", app.overridden_price * 100, 'overridden_price')

@cost_calculation.ArtShowApplication
def panel_cost(app):
return ("General Panel", c.COST_PER_PANEL * 100, app.panels) if app.panels else None
return ("General Panel", c.COST_PER_PANEL * 100, app.panels, None) if app.panels else None

@cost_calculation.ArtShowApplication
def table_cost(app):
return ("General Table", c.COST_PER_TABLE * 100, app.tables) if app.tables else None
return ("General Table", c.COST_PER_TABLE * 100, app.tables, None) if app.tables else None

@cost_calculation.ArtShowApplication
def mature_panel_cost(app):
return ("Mature Panel", c.COST_PER_PANEL * 100, app.panels_ad) if app.panels_ad else None
return ("Mature Panel", c.COST_PER_PANEL * 100, app.panels_ad, None) if app.panels_ad else None

@cost_calculation.ArtShowApplication
def mature_table_cost(app):
return ("Mature Table", c.COST_PER_TABLE * 100, app.tables_ad) if app.tables_ad else None
return ("Mature Table", c.COST_PER_TABLE * 100, app.tables_ad, None) if app.tables_ad else None

@cost_calculation.ArtShowApplication
def mailing_fee_cost(app):
return ("Mailing fee", c.ART_MAILING_FEE * 100) if app.delivery_method == c.BY_MAIL else None
return ("Mailing fee", c.ART_MAILING_FEE * 100, None) if app.delivery_method == c.BY_MAIL else None


Attendee.cost_changes = {
Expand Down Expand Up @@ -77,25 +77,27 @@ def badge_cost(attendee):
else:
label = "{} badge for {}".format(attendee.badge_type_label, attendee.full_name)

return (label, cost)
return (label, cost, None)

@cost_calculation.Attendee
def badge_upgrade_cost(attendee):
if attendee.badge_type in c.BADGE_TYPE_PRICES:
return ("{} badge upgrade for {}".format(attendee.badge_type_label, attendee.full_name), attendee.calculate_badge_prices_cost() * 100)
return ("{} badge upgrade for {}".format(attendee.badge_type_label, attendee.full_name),
attendee.calculate_badge_prices_cost() * 100, 'badge_type')

@cost_calculation.Attendee
def shipping_fee_cost(attendee):
if attendee.badge_status == c.DEFERRED_STATUS and attendee.amount_extra:
return ("Merch Shipping Fee", attendee.calculate_shipping_fee_cost() * 100)
return ("Merch Shipping Fee", attendee.calculate_shipping_fee_cost() * 100, None)

@cost_calculation.Attendee
def donation_cost(attendee):
return ("Extra Donation", attendee.extra_donation * 100) if attendee.extra_donation else None
return ("Extra Donation", attendee.extra_donation * 100, 'extra_donation') if attendee.extra_donation else None

@cost_calculation.Attendee
def kickin_cost(attendee):
return ("Kickin ({})".format(attendee.amount_extra_label), attendee.amount_extra * 100) if attendee.amount_extra else None
return ("Kickin ({})".format(attendee.amount_extra_label),
attendee.amount_extra * 100, 'amount_extra') if attendee.amount_extra else None

@credit_calculation.Attendee
def age_discount(attendee):
Expand All @@ -105,13 +107,13 @@ def age_discount(attendee):
else:
age_discount = attendee.age_discount * 100

return ("Age Discount", age_discount)
return ("Age Discount", age_discount, None)

@credit_calculation.Attendee
def group_discount(attendee):
if c.GROUP_DISCOUNT and attendee.qualifies_for_discounts and not attendee.age_discount and (
attendee.promo_code_groups or attendee.group):
return ("Group Discount", c.GROUP_DISCOUNT * 100 * -1)
return ("Group Discount", c.GROUP_DISCOUNT * 100 * -1, None)


Group.cost_changes = {
Expand All @@ -124,25 +126,25 @@ def group_discount(attendee):
def table_cost(group):
table_count = int(float(group.tables))
if table_count and group.auto_recalc:
return ("{} Tables".format(table_count), sum(c.TABLE_PRICES[i] for i in range(1, 1 + table_count)) * 100)
return ("{} Tables".format(table_count), sum(c.TABLE_PRICES[i] for i in range(1, 1 + table_count)) * 100, None)

@cost_calculation.Group
def badge_cost(group):
cost_table = defaultdict(int)

if not group.auto_recalc:
return None
return

for attendee in group.attendees:
if attendee.paid == c.PAID_BY_GROUP and attendee.badge_cost:
cost_table[attendee.badge_cost * 100] += 1

return ("Group badge ({})".format(group.name), cost_table)
return ("Group badge ({})".format(group.name), cost_table, None)

@cost_calculation.Group
def set_cost(group):
if not group.auto_recalc:
return ("Custom fee for group {}".format(group.name), group.cost * 100)
return ("Custom fee for group {}".format(group.name), group.cost * 100, None)


@cost_calculation.Attendee
Expand All @@ -159,4 +161,4 @@ def promo_code_group_cost(attendee):
return

return ("Group badge ({})".format(attendee.promo_code_groups[0].name if attendee.promo_code_groups
else getattr(attendee, 'name', 'Unknown')), cost_table)
else getattr(attendee, 'name', 'Unknown')), cost_table, None)
12 changes: 10 additions & 2 deletions uber/site_sections/reg_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,17 @@ def refresh_receipt_txn(self, session, id='', **params):

if txn.amount_left > 0:
prior_amount = txn.amount - txn.amount_left
new_amount = txn.update_amount_refunded()
if prior_amount != new_amount:
new_amount, last_refund_id = txn.update_amount_refunded()
if prior_amount < new_amount:
messages.append("Refund amount updated from {} to {}.".format(format_currency(prior_amount / 100),
format_currency(new_amount / 100)))
session.add(ReceiptTransaction(
receipt_id=txn.receipt_id,
refund_id=last_refund_id,
amount=(new_amount - prior_amount) * -1,
desc="Automatic refund of Stripe transaction " + txn.stripe_id,
who=AdminAccount.admin_name() or 'non-admin'
))

session.commit()
else:
Expand All @@ -351,6 +358,7 @@ def refresh_receipt_txn(self, session, id='', **params):
@ajax
def refund_receipt_txn(self, session, id='', **params):
txn = session.receipt_transaction(id)
return {'error': float(params.get('amount', 0)) * 100}

error = session.refund_item_or_txn(amount=float(params.get('amount', 0)) * 100, txn=txn)
if error:
Expand Down
53 changes: 31 additions & 22 deletions uber/templates/reg_admin/receipt_items.html
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@
callback: function (result) {
$("#refundTxnDiv").html(form);
if(result) {
$.post('refund_receipt_txn', {csrf_token: csrf_token, id: txnId, amount: $("#refundTxnForm [name='amount']").val() || 0}, function (response) {
$.post('refund_receipt_txn', {csrf_token: csrf_token, id: txnId, amount: refundAmt}, function (response) {
toastr.clear();
if (response.error) {
toastr.error(response.error);
} else {
toastr.info('Transaction refunded');
toastr.info(response.message);
updateRow(response.refunded, ' td:last #amt-refunded', " ($" + (response.refund_total / 100).toFixed(2) + " refunded)");
updateTotal(response.new_total, response.disable_button);
}
Expand All @@ -205,6 +205,17 @@
});
};

var compOrUndoRefundItem = function (page_handler, itemId, amount) {
$.post(page_handler, {csrf_token: csrf_token, id: itemId, amount: amount}, function (response) {
toastr.clear();
if (response.error) {
toastr.error(response.error);
} else {
window.location = 'receipt_items?id={{ model.id }}&message=' + response.message;
}
}, 'json');
}

var refundItem = function (itemId, amount, count, canRevert) {
var bootboxBtns = {
cancel: {
Expand All @@ -214,17 +225,10 @@
comp: {
label: 'Comp and Refund',
className: 'btn-success',
callback: function () {
$.post('comp_refund_receipt_item', {csrf_token: csrf_token, id: itemId, amount: amount.substring(1)}, function (response) {
toastr.clear();
if (response.error) {
toastr.error(response.error);
} else {
toastr.info('Transaction refunded');
updateRow(response.refunded, ' td:last #amt-refunded', " ($" + (response.refund_total / 100).toFixed(2) + " refunded)");
updateTotal(response.new_total, response.disable_button);
}
}, 'json');
callback: function (result) {
if(result) {
compOrUndoRefundItem('comp_refund_receipt_item', itemId, amount.substring(1))
}
}
}
}
Expand All @@ -233,6 +237,11 @@
bootboxBtns.revert = {
label: 'Undo and Refund',
className: 'btn-danger',
callback: function (result) {
if(result) {
compOrUndoRefundItem('undo_refund_receipt_item', itemId, amount.substring(1))
}
}
}
}

Expand Down Expand Up @@ -478,16 +487,16 @@ <h2>Receipt{% if other_receipts %}s{% endif %} for {% if model.attendee %}{{ mod
{% endif %}
</td>
<td>
{% if item.refundable %}
{% if item.refundable and not item.method %} {# disabling transaction refunds for now #}
<button class="btn btn-sm btn-warning"
onClick="
{% if item.method %}
refundTxn('{{ item.id }}', '{{ (item.amount_left / 100)|format_currency }}')
{% else %}
refundItem('{{ item.id }}', '{{ (item.amount / 100)|format_currency }}', '{{ item.count }}', '{{ item.revert_change|yesno }}')
{% endif %}"
>
Refund
onClick="
{% if item.method %}
refundTxn('{{ item.id }}', '{{ (item.amount_left / 100)|format_currency }}')">
Refund
{% else %}
refundItem('{{ item.id }}', '{{ (item.amount / 100)|format_currency }}', '{{ item.count }}', '{{ item.revert_change|yesno }}')">
Refund Item
{% endif %}
</button>
{% endif %}
</td>
Expand Down
18 changes: 11 additions & 7 deletions uber/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,11 +1085,13 @@ def create_new_receipt(cls, model, create_model=False, items=None):
item = calculation(model)
if item:
try:
desc, cost, count = item
desc, cost, col_name, count = item
except ValueError:
# Unpack list of wrong size (no quantity provided).
desc, cost = item
desc, cost, col_name = item
count = 1

default_val = getattr(model.__class__(), col_name, None) if col_name else None
if isinstance(cost, Iterable):
# A list of the same item at different prices, e.g., group badges
for price in cost:
Expand All @@ -1098,16 +1100,18 @@ def create_new_receipt(cls, model, create_model=False, items=None):
desc=desc,
amount=price,
count=cost[price],
who=AdminAccount.admin_name() or 'non-admin'
who=AdminAccount.admin_name() or 'non-admin',
revert_change={col_name: default_val} if col_name else {}
))
else:
receipt_items.append((desc, price, cost[price]))
elif receipt:
receipt_items.append(ReceiptItem(receipt_id=receipt.id,
desc=desc,
amount=cost,
count=count,
who=AdminAccount.admin_name() or 'non-admin'
desc=desc,
amount=cost,
count=count,
who=AdminAccount.admin_name() or 'non-admin',
revert_change={col_name: default_val} if col_name else {}
))
else:
receipt_items.append((desc, cost, count))
Expand Down

0 comments on commit 53b0561

Please sign in to comment.