Skip to content

Commit

Permalink
Merge pull request #4258 from magfest/pc-group-refunds
Browse files Browse the repository at this point in the history
Updates to receipt refunds plus adding refunding of group badges
  • Loading branch information
kitsuta authored Sep 30, 2023
2 parents 4a701eb + fc53f86 commit aced37f
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 79 deletions.
31 changes: 20 additions & 11 deletions uber/models/commerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ def all_sorted_items_and_txns(self):

@property
def total_processing_fees(self):
return sum([txn.calc_partial_processing_fee(txn.amount) for txn in self.refundable_txns])
return sum([txn.calc_processing_fee(txn.amount) for txn in self.refundable_txns])

@property
def remaining_processing_fees(self):
return sum([txn.calc_partial_processing_fee(txn.amount_left) for txn in self.refundable_txns])
return sum([txn.calc_processing_fee(txn.amount_left) for txn in self.refundable_txns])

@property
def open_receipt_items(self):
Expand Down Expand Up @@ -189,6 +189,10 @@ def txn_total(self):

@property
def total_str(self):
if self.closed:
return "{} in {}".format(format_currency(abs(self.txn_total / 100)),
"Payments" if self.txn_total >= 0 else "Refunds")

return "{} in {} and {} in {} = {} owe {}".format(format_currency(abs(self.item_total / 100)),
"Purchases" if self.item_total >= 0 else "Credit",
format_currency(abs(self.txn_total / 100)),
Expand Down Expand Up @@ -283,7 +287,8 @@ def available_actions(self):

@property
def refundable(self):
return self.charge_id and self.amount_left and self.amount > 0
return not self.receipt.closed and self.charge_id and self.amount > 0 and \
self.amount_left and self.amount_left != self.calc_processing_fee()

@property
def stripe_url(self):
Expand Down Expand Up @@ -313,8 +318,9 @@ def stripe_id(self):
# Return the most relevant Stripe ID for admins
return self.refund_id or self.charge_id or self.intent_id

def get_processing_fee(self):
if self.processing_fee:
@property
def total_processing_fee(self):
if self.processing_fee and self.amount == self.txn_total:
return self.processing_fee

if c.AUTHORIZENET_LOGIN_ID:
Expand All @@ -326,14 +332,17 @@ def get_processing_fee(self):

return intent.charges.data[0].balance_transaction.fee_details[0].amount

def calc_partial_processing_fee(self, refund_amount):
def calc_processing_fee(self, amount=0):
from decimal import Decimal

if not refund_amount:
return 0
if not amount:
if self.processing_fee:
return self.processing_fee

amount = self.amount

refund_pct = Decimal(refund_amount) / Decimal(self.txn_total)
return refund_pct * Decimal(self.get_processing_fee())
refund_pct = Decimal(amount) / Decimal(self.txn_total)
return int(refund_pct * Decimal(self.total_processing_fee))

def check_stripe_id(self):
# Check all possible Stripe IDs for invalid request errors
Expand Down Expand Up @@ -392,7 +401,7 @@ def check_paid_from_stripe(self, intent=None):
intent = intent or self.get_stripe_intent()
if intent and intent.status == "succeeded":
new_charge_id = intent.charges.data[0].id
ReceiptManager.mark_paid_from_intent_id(self.intent_id, new_charge_id)
ReceiptManager.mark_paid_from_stripe_intent(intent)
return new_charge_id

def update_amount_refunded(self):
Expand Down
20 changes: 17 additions & 3 deletions uber/models/promo_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pytz import UTC
from dateutil import parser as dateparser
from residue import CoerceUTF8 as UnicodeText, UTCDateTime, UUID
from sqlalchemy import func, select, CheckConstraint
from sqlalchemy import exists, func, select, CheckConstraint
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.schema import Index, ForeignKey
from sqlalchemy.types import Integer
Expand Down Expand Up @@ -172,13 +172,17 @@ def email(self):

@hybrid_property
def total_cost(self):
return sum(code.cost for code in self.promo_codes if code.cost)
return sum(code.cost for code in self.paid_codes if code.cost)

@total_cost.expression
def total_cost(cls):
return select([func.sum(PromoCode.cost)]
).where(PromoCode.group_id == cls.id
).where(PromoCode.group_id == cls.id).where(PromoCode.refunded == False
).label('total_cost')

@property
def paid_codes(self):
return [code for code in self.promo_codes if not code.refunded]

@property
def valid_codes(self):
Expand Down Expand Up @@ -456,6 +460,16 @@ def uses_remaining(cls):
def uses_remaining_str(self):
uses = self.uses_remaining
return 'Unlimited uses' if uses is None else '{} use{} remaining'.format(uses, '' if uses == 1 else 's')

@hybrid_property
def refunded(self):
return self.used_by and self.used_by[0].badge_status == c.REFUNDED_STATUS

@refunded.expression
def refunded(cls):
from uber.models import Attendee
return exists().select_from(Attendee).where(cls.id == Attendee.promo_code_id
).where(Attendee.badge_status == c.REFUNDED_STATUS)

@presave_adjustment
def _attribute_adjustments(self):
Expand Down
27 changes: 23 additions & 4 deletions uber/payments.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ def send_authorizenet_txn(self, txn_type=c.AUTHCAPTURE, **params):
log.debug(f"Transaction {self.tracking_id} request successful. Transaction ID: {auth_txn_id}")

if txn_type in [c.AUTHCAPTURE, c.CAPTURE]:
ReceiptManager.mark_paid_from_intent_id(params.get('intent_id'), auth_txn_id)
ReceiptManager.mark_paid_from_ids(params.get('intent_id'), auth_txn_id)
else:
error_code = str(response.transactionResponse.errors.error[0].errorCode)
error_msg = str(response.transactionResponse.errors.error[0].errorText)
Expand Down Expand Up @@ -1062,16 +1062,35 @@ def add_receipt_item_from_param(self, model, receipt, param_name, params, func_n
log.error(str(e))

@staticmethod
def mark_paid_from_intent_id(intent_id, charge_id):
def mark_paid_from_stripe_intent(payment_intent):
if not payment_intent.charges.data:
log.error(f"Tried to mark payments with intent ID {payment_intent.id} as paid but that intent doesn't have a charge!")
return []

if payment_intent.status != "succeeded":
log.error(f"Tried to mark payments with intent ID {payment_intent.id} as paid but the charge on this intent wasn't successful!")
return []

ReceiptManager.mark_paid_from_ids(payment_intent.id, payment_intent.charges.data[0].id)

@staticmethod
def mark_paid_from_ids(intent_id, charge_id):
from uber.models import Attendee, ArtShowApplication, MarketplaceApplication, Group, ReceiptTransaction, Session
from uber.tasks.email import send_email
from uber.decorators import render

session = Session().session
matching_txns = session.query(ReceiptTransaction).filter_by(intent_id=intent_id).filter(
ReceiptTransaction.charge_id == '').all()


if not matching_txns:
log.debug(f"Tried to mark payments with intent ID {intent_id} as paid but we couldn't find any!")
return []

for txn in matching_txns:
if not c.AUTHORIZENET_LOGIN_ID:
txn.processing_fee = txn.calc_processing_fee()

txn.charge_id = charge_id
session.add(txn)
txn_receipt = txn.receipt
Expand Down
2 changes: 1 addition & 1 deletion uber/site_sections/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def stripe_webhook_handler(self):

if event and event['type'] == 'payment_intent.succeeded':
payment_intent = event['data']['object']
matching_txns = ReceiptManager.mark_paid_from_intent_id(payment_intent['id'], payment_intent.charges.data[0].id)
matching_txns = ReceiptManager.mark_paid_from_stripe_intent(payment_intent)
if not matching_txns:
cherrypy.response.status = 400
return "No matching Stripe transactions"
Expand Down
8 changes: 4 additions & 4 deletions uber/site_sections/preregistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ def prereg_payment(self, session, message='', **params):
pending_attendee = session.query(Attendee).filter_by(id=attendee.id).first()
if pending_attendee:
pending_attendee.apply(attendee.to_dict(), restricted=True)
if attendee.badges:
if attendee.badges and pending_attendee.promo_code_groups:
pc_group = pending_attendee.promo_code_groups[0]
pc_group.name = attendee.name

Expand All @@ -755,6 +755,9 @@ def prereg_payment(self, session, message='', **params):
session.add_codes_to_pc_group(pc_group, pc_codes - pending_codes)
elif pc_codes < pending_codes:
session.remove_codes_from_pc_group(pc_group, pending_codes - pc_codes)
elif attendee.badges:
pc_group = session.create_promo_code_group(pending_attendee, attendee.name, int(attendee.badges) - 1)
session.add(pc_group)
elif pending_attendee.promo_code_groups:
pc_group = pending_attendee.promo_code_groups[0]
session.delete(pc_group)
Expand Down Expand Up @@ -1350,9 +1353,6 @@ def abandon_badge(self, session, id):
if attendee.paid == c.HAS_PAID:
attendee.paid = c.REFUNDED

if attendee.in_promo_code_group:
attendee.promo_code = None

# if attendee is part of a group, we must delete attendee and remove them from the group
if attendee.group:
session.assign_badges(
Expand Down
Loading

0 comments on commit aced37f

Please sign in to comment.