Skip to content

Instantly share code, notes, and snippets.

@joshuapaling
Last active October 22, 2020 05:29
Show Gist options
  • Save joshuapaling/66d7179e87175ac0c3ababb7507a6b3c to your computer and use it in GitHub Desktop.
Save joshuapaling/66d7179e87175ac0c3ababb7507a6b3c to your computer and use it in GitHub Desktop.

How promo refunds work now

See https://github.com/brandsExclusive/svc-payment/pull/584

When a customer buys a $100 item with a 10% promo discount, the customer sees it as "I only paid $90". But our system sees it as "The customer paid $100 total, made up of $90 stripe, and $10 promo". The "promo" is just another payment type, like credits, stripe, paypal, razorpay, etc. And the "promo" payment type is thought of as "cash spent by the marketing team on advertising to the customer who used the promo".

Promo payments are never refunded. Users of course don't care about this (they were never aware of the hidden "promo" payment they made), but our books do.

If you refund a full item, the full amount (excluding promo payments) is refunded. If you do a partial refund, we leave it to the admin to ensure they're refunding the right amount - considering promos. But if a $100 order has a $10 promo, you can never refund more than $90.

This saves problems for the devs, because refunding promos is hard - eg how much promo do you refund if you're doing a partial refund of an item that's part of an order with two other items, and one of the other items has already been partially refunded, and the user used a 10% promo with a minimum spend amount that's no longer met after the item is refunded?

But, it creates problems for finance because promo payments are considered 'marketing spend'. Refunded promos should then be 'marketing spend reverted'. Us not refunding promo payments makes it look like they're 'marketing spend that is now cash we're sitting on from sales' when it's not - the "sale" was refunded, and the promo spend should have been, too.

How Kamia (finance) wants promo refunds to work

Regardless of partial or full refund, fixed refund fee, etc... Any refunded item, cancelled or not: refund promo component proportionally, IGNORING fixed fees

Consider $100 item, paid with $90 cash and $10 promo.

Eg 1. Refund 50%, but with fix fee of $20: a) refund 50% = $45, but then deduct $20 fee = $25 of cash refunded b) refund $5 of promo (ie, 50%, ignore fixed refund fee)

Eg 2. Refund $80 (ie, CS enters $80 in amount to refund): a) $80 is 80%, so we refund 0.8 * 90 = $72 cash, and 0.8 * 10 = $8 promo.

multi-item orders

To calculate the "promo component" in the case of multiple items purchased in a single "payment plan", we say regardless of FIXED or PERCENTAGE promo, refund proportionally, based on the total payment value and the promo component. Note you can have a single order that has: a) 2 $50 items paid for with $80 stripe, $20 promo. b) an add-on, purchased later, with a $40 stripe payment on a separate card. These will be two separate payment plans. When refunding b), we should NOT consider promo component from a), and vica versa.

Eg 1. two $50 items purchased with $90 cash and $10 promo: If we refund the first item, refund $45 cash and $5 promo, regardless of whether it was a $10 fixed or 10% promo.

back-filling data

In most cases, we can also backfill the data, so we revert promo spend for orders that were refunded in the past. Do finance want this? NICE TO HAVE, NOT MUST HAVE. OUT OF SCOPE FOR NOW.

SCRATCHED - IGNORE: For promo refunds, what is fair to customer and works for us?

This was Joss' suggestion but Kamia (finance) preferred a simpler approach, detailed above

The user should get the savings of the promo, as it would apply to the new contents / total cost of their order, after the cost being refunded is taken out. So:

a) Figure out how much the promo would save them BEFORE refunding item.
b) Figure out how much the promo would save them AFTER refunding item (which could be $0 if the min spend amount is no longer met)

The difference between those two is the promo amount to refund. Process that promo refund first, then process the remaining amount between other payment methods.

Note: users can have max 1 promo per order. So eg. if they buy addons in a separate transaction, they can apply a promo either to the addons payment, or the original order payment, but not both.

Example 1 - FIXED promo:

2 * $50 items, with $10 fixed promo. $90 stripe payment, plus $10 promo payment.

Refund first item:
a) value before = $100; promo value before = $10
b) value after = $50; promo value after = $10
=> promo amount to refund is $0

Refund second item:
a) value before = $50; promo value before = $10
b) value after = $0; promo value after = $0
=> promo amount to refund is $10

Example 2 - same as 1 but with PERCENTAGE promo:

2 * $50 items, with 10% promo. $90 stripe payment, plus $10 promo payment.

Refund first item:
a) value before = $100; promo value before = $10 (ie, 10% of $100)
b) value after = $50; promo value after = $5 (ie, 10% of $50)
=> promo amount to refund is $10 - $5 = $5

Refund second item:
a) value before = $50; promo value before = $5 (ie, 10% of $50)
b) value after = $0; promo value after = $0 (ie, 10% of $0)
=> promo amount to refund is $5 - $0 = $5

Example 3 - edge case

2 * $50 item, with $60 fixed promo, min spend is $80.

Refund first item:
a) value before = $100; promo value before = $60
b) value after = $50; promo value after = $0 (min spend not met)
=> promo amount to refund is $60, which is more than the total $50 to refund for this item. So, refund only $50 of promo payment, leaving $10 of (invalid - min spend not met) promo payment to cover the remaining item in the order.

Refund second item:
a) value before = $50; promo value before = $0 (min spend not met)
b) value after = $0; promo value after = $0 (min spend not met)
=> promo amount to refund is $0 because min spend was not met. But due to the glitch above, we still have $10 of promo payment that should be refunded. So, special case logic should be: if promo amount AFTER is $0, then automatically refund ANY remaining promo payment on the order first. Don't bother checking against the BEFORE value from a)

Can this be done in payment svc?

YES - but only for recent orders, that have payment.item_ids filled. We could try backfill payment.item_ids for old orders, but it'd be tricky for edge cases. We could do it just for the easy cases, which would be 95%+ of orders?

Steps:

Find all payments (including the promo payment) with transaction keys the same as the promo payment. This gets total cost that the promo should apply to originally. Deduct any refunds (including promo refunds) from the specific items paid for by those payments (can only be done for recent payments which have item_ids column filled). This gets total cost AFTER this refund.

What about partially refunded items?

If $80 of a $100 item is refunded, leaving $20 as a "refund fee", that refund fee would count towards the total amount the promo applies to. Example - consider a $100 item purchased with a 10% promo:

Original payments is $100 (90 stripe + 10 promo)
refund = $80 (72 stripe + 8 promo)
fee: $20 (18 stripe + 2 promo)

So, customer gets $72 of their original $90 cash payment back. They effectively pay a $18 refund fee because their promo applies to the refund fee, too.

The only way to get around this would be to deduct the entire cost of any cancelled items, which would mean all the logic must live in order svc, not payment svc, and order would have to pass up the "promo amount" as a separate param when doing a refund. Probably not worth it.

what about payments that don't have payment.item_ids col populated?

This includes all LE orders prior to a week ago, and all LED orders. We could either:

a) keep it as it is now. Clients detect these cases, check the refund amount and don't refund promos. (BAD - logic distributed between services)

b) Clients treat promo as any other payment. Payment svc prioritises promo refunds after credits, but before gateway refunds. Customer Service deal with any complaints.

c) Clients treat promo as any other payment. Payment svc prioritises promo refunds LAST, after credits / gateway refunds. Customers win by keeping full value of promo.

I'd suggest b)

other considerations

LED should populate payment.item_ids col - we're going down a path were payment svc needs this. Payment svc should maybe then make it not nullable.

Refunds to credit: must make sure we never refund a promo payment to credit.

Downgrade to BNBL, repurchase with credits: must make sure this works OK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment