#Bassil-overcharge

1 messages ยท Page 1 of 1 (latest)

cyan sapphire
winged parcel
#

Hello

#

we're here but what you shared, that sounds like possibly an integration issue. Happy to talk through it

zealous aurora
#

Hi there yes please

#

Second time it happened today and I feel so embarrassed from my customer. He was charged 5,939.76AED when he should have only been charged AED 2,190.37

#

The meta data for the order is correct but amount charged is wrong

#

here is the payment ID

winged parcel
#

mind sharing the two PaymentIntent IDs?

zealous aurora
#

pi_3KC1chBStawTvwlL0iCZkGBJ

#

This is for the most recent one, the first incident is the following:

#

pi_3KBvsABStawTvwlL2yZN9FPQ

winged parcel
#

digging

zealous aurora
#

Ah

winged parcel
#

ok so both of these were PaymentIntents created by your code. And confirmed on the client-side

I thought this would be a "duplicate" charge issue, that your integration created the charge in quick succession

but that is NOT the case

these charges are ~6 hrs apart

so really, your code asked us to create two different charges, which were paid by two different cards.

Your metadata has tagged them as different customers too so I'm not clear on what the issue is

zealous aurora
#

I found the culprit I think...

#

Okay here it is

#

I had a theory and checked my website access logs and confirmed the theory

#

When customers click Buy Now, it adds the product to their cart and takes them to the checkout page.

#

Now

#

They clicked back and viewed other products and then clicked buy now so now they have multiple items in their cart right? on the checkout page

#

They clicked remove on the products they dont want... but the payment intent was created and NOT updated since the update only happens when they click the pay now button

#

I need to a) update new amount when they click pay now and b) update payment intent when they remove an item from their cart

#

i think this may be the issue I will double check

#

Oh the issue was the customer was charged more than they should have

winged parcel
#

ah

zealous aurora
#

both customers faced same issue

#

so I had to refund both the difference

winged parcel
#

so yeah the PaymentIntent not being updated to reduce the amount based on the reduced products

#

yeah that would be the issue here

zealous aurora
#

going to try to do this method and let you know if it works ๐Ÿ™‚

winged parcel
#

sure

#

it should work really

zealous aurora
#

boom found the issue

#

when they click Pay, it should update payment intent which it does due to the meta data

#

but the amount is only updated if they use code as per my code

#
        if code_valid:
            payment_intent = request.session['payment_intent'].strip()
            stripe.api_key = STRIPE_SECRET_KEY
            stripe.PaymentIntent.modify(
                payment_intent,
                amount=int(str(cart_subtotal_in_aed).replace('.', '')),
                metadata={'code': code, 'code_valid': code_valid,
                          'percent_off': percent_off, 'discount': discount,
                          'cart_subtotal_in_aed_with_code': cart_subtotal_in_aed},
            )

#

I need to update the payment amount regardless if they use a code or not

winged parcel
#

yep

#

good find

zealous aurora
#

haha glad I found it, I felt so bad for my customers

#

I wanted to literally dig a hole and put my face in it

#

thank you so much for the brain storming session

#

๐Ÿ˜„

winged parcel
#

yeah tough situation to be in, one edge case causes charging an incorrect amount and unhappy customers

#

so I'm glad you found it quickly!

zealous aurora
#

Thank you ๐Ÿ™‚ Do you think I should update payment intent when removing item from cart as a fail safe too?

#

Yay it worked ๐Ÿ™‚

winged parcel
#

Do you think I should update payment intent when removing item from cart as a fail safe too?

is that different from the case you fixed?

zealous aurora
#

Well currently the payment intent only updates when they click the pay now stripe button

#

I basically make an ajax call to update the payment server side, and then go ahead with the charge

#

Ill just call the payment intent on cart removal too i guess

#
 $.ajax({
                    type: "POST",
                    url: '{% url 'store:update_payment_intent' %}',
                    data: $('#payment-form').serialize(),
                    success: function (response) {
                    }
                });
winged parcel
#

yeah depends, on the pay button is one canonical path where you update the PaymentIntent depending on the state of the cart

but doesn't hurt to do it before

zealous aurora
#

hmm weird I was testing a transaction locally

#

AED 6,200.00

but it charged 620

#

Meta data seems to be correct:
cart_subtotal_in_aed_less_code
6200.0

#

My python code to update the amount is ```python
payment_intent = request.session['payment_intent'].strip()
stripe.api_key = STRIPE_SECRET_KEY
stripe.PaymentIntent.modify(
payment_intent,
amount=int(str(cart_subtotal_in_aed).replace('.', '')),
)

#

I suspect because python is formatting this number to 1 decimal place .0

#

maybe if it was .00 it would charge the correct amount, hmm what do you suggest, a way to format the total that stripe can read

#

because stripe doesn't allow decimals in the number we provide

winged parcel
#

Stripe just takes in amount in cents so to charge AED 6200.00, you pass amount: 620000

#

I would say treat amounts in cents, until you have to pass it under metadata and convert it to decimal there by dividing by 100

zealous aurora
#

Oh i did ```python
cart_subtotal_in_aed = format(round(cart_subtotal_in_aed - discount, 2), '.2f')

winged parcel
#

what was the amount on the PaymentIntent you created

#

this part

is probably prone to math/rounding/significant place errors due to the conversions etc (I'm not a python expert)

my recommendation would be to treat the raw amount always in cents and only convert in other places when needed

#

cause what the PaymentIntent charges all depends on one field, the amount
field/param on the PaymentIntent

#

so you want that one field to be resilient to any conversions/miscalculation mistakes

#

and in your code where you change/modify the amount for display/metadata purposes, you do any math/conversion on it there but otherwise your system communicates to Stripe purely in an untouched cents value

zealous aurora
#

hmm

#

so if the amount was 6200 how should i convert it to a format stripe accepts

#

or imagine it was 6200.52

winged parcel
#

6200.52 == 6200 dollars and 52 cents
Stripe accepts a value only in the lowest common denominator of the currency aka cents in this case

so you pass Stripe 620052
which is "6 hundred and twenty thousand and 52 cents"

zealous aurora
#

Hmm okay but our currency is in AED, if i am getting a number with a decimal

#

jsut remove the decimal?

winged parcel
#

multiple by 100 in that case yes to remove the decimal and convert to an int

zealous aurora
#

oh

#

so x 100 and convert to int

#

no need to do replace('.','')

#

6200.52 * 100 = 620,052 which in int form is 620052

#

excellent

#

My man out here saving companies from crashing haha thanks man

#

My math is correct I assume

winged parcel
#

basically that your raw amount number should be in int and that is what you should be basing everything off of

and then when you need to tell your system "hey this was $124.56 AED" you take the raw amount 12456 and divide it by 100 and display as a decimal or string

but otherwise you keep calculations to just cents so there are no rounding issues etc

#

My man out here saving companies from crashing haha thanks man
haha here to help

zealous aurora
#

Does this look right

#
    def calculate_order_amount(items):
        # Adds cart items to payment intent
        cart_items_list = ''
        cart_subtotal_in_aed = 0.0
        cart_cogs_in_aed = 0.0
        for product_id, quantity in request.session['cart'].items():
            cart_items_list += (product_id + ':' + quantity + ',')
            product = TyreProducts.objects.get(pk=product_id)
            cart_subtotal_in_aed += (product.price_in_aed * float(quantity))
            cart_cogs_in_aed += (product.cogs_in_aed * float(quantity))
        total = round(cart_subtotal_in_aed, 2) * 100

        return int(str(total).replace('.', ''))

winged parcel
#

my suggestion was the opposite

that you keep the value always as an int and do all math/updates with the int aka "cent" value of the amounts

and then transform it by dividing by 100 when you need to pass into metadata

what I wanted to avoid was the thing like

            cart_subtotal_in_aed += (product.price_in_aed * float(quantity))

like in my eyes it would be simpler to just treat everything as cents and ints the quantity * amount multiplication

e.g. 12456 * 5 qty
and then later down the line transform it by /100

zealous aurora
#

Hmm I see so since the price of a item is a float by default from my database, I would simply remove the . and then convert to int

#
int(str(product.cogs_in_aed).replace('.',''))
winged parcel
#

that works though would it not work to int(product.cogs_in_aed * 100)

string conversion doesn't sound right, and the removal of decimal by char replace

zealous aurora
#

Oh that makes sense, I was afraid we would still have a decimial after multiplying by 100

#

but I just checked, as long as there is only 2 decimal places, multiplying by 100 will remove the decimal correct/

winged parcel
#

but the int() gets rid of whatever is after the decimal I believe

zealous aurora
#

so like ```python
int(round(product.cogs_in_aed,2) * 100)

#

Just in case somehow there is more than 2 decimal places in the number

#

Because for example 9283.383 * 100 is 928,338.3

#

and thus will cause an error, so by rounding off to 2 decimals before converting, should solve it?

winged parcel
#

and thus will cause an error
which part will cause error? round then int won't (even just int probably rounds as well)

zealous aurora
#

Oh I see Let me try in code, I was doing the calc on my calculator

#

woops haha sorry its been a long day

#

trying now

#

wow magic haha

#

it worked

#

You the man!

#

Now its so much easier and simpler, idk why the heck I was removing . with .replace and all that speghetti code haha