Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

tykus's avatar

Also, for my benefit, what is finalTotal in comparison to total and finalTotalAmount? My interpretation is finalTotalAmount is the amount after discount has been applied; total is the amount before discount.

Stepping through the code in your Gist, these are the expected values I would expect to see:

        validate: function() {
            this.$http.get('/coupons/' + this.coupon.code)
                .success(function(coupon, finalTotal) { // what is finalTotal???
                    this.coupon = coupon; // this.coupon.discount = 10
                    this.valid = true;
                    this.coupon.description = 'Great! You entered a valid coupon.';

                    discountPercentage = this.coupon.discount/100; // 10 / 100 = 0.1
                    discountAmount = this.total*(this.discountPercentage); // 27.99 * 0.1 = 2.7.9
                    discountedTotal = this.total - this.discountAmount; // 27.99 - 2.799 = 25.191
                    this.total = Math.round(this.discountedTotal * 100); // 2519 <== £25.19
                    this.$set('finalTotalAmount', this.total);

                })

Is your project on github?

tykus's avatar

The following works... based on the assumptions that: (1) $http.get('/coupons/' + this.coupon.code) returns to you a pure number (e.g. 10 meaning 10% etc.) (2) $http.get('/cartTotal') returns to you the amount in pennies

In your Vue:

    new Vue({
      el: '#coupons',

      data: {
        coupon: {
          code: '',
          description: '',
          discount: 0
        },
        valid: false,
        finalTotal: 0,
        finalTotalAmount: 0
      },
      
      ready: function() {
        this.getTotal();
      },
      
      methods: {
        getTotal: function() {
          this.$http.get('/cartTotal', function(total) {
            this.finalTotal= this.finalTotalAmount = total;
          },
        },
        validate: function() {
          this.$http.get('/coupons/' + this.coupon.code) 
                    .success(function(coupon) {
                      this.coupon = coupon;
                      this.valid = true;
                      this.coupon.description = 'Great! You entered a valid coupon.';

                      this.calculateDiscountedFinalAmount();
                    })
                    .error(function() {
                      this.coupon.code = '';
                      this.coupon.description = 'Sorry, that coupon does not exist.';
                    });
        },
        calculateDiscountedFinalAmount: function () {
          discountPercentage = this.coupon.discount / 100;
          discountAmount = this.finalTotal * discountPercentage;
          discountedTotal = this.finalTotal - discountAmount;
          return this.finalTotalAmount = Math.round(discountedTotal);
        }
      }
    });

In your view:

...
Total: &pound; @{{ finalTotalAmount / 100}} </p>        <!-- POUNDS --> 

{!! Form::open(['url' => 'stripeSuccess', 'id' => 'purchase-form', 'class' => 'form-horizontal']) !!}

            <input type="hidden" name="finalTotalAmount" value="@{{ finalTotalAmount }}">       <!-- PENNIES --> 
... 

Can I also add that setting the amount to be charged on the client-side is a really bad idea.

theUnforgiven's avatar

If I apply a discount it doesn't get passed to Stripe either now. Also ideally I want to value on screen to updated to show the user.

here's the full JS

new Vue({
    el: '#coupons',

    data: function() {
        return {
            coupon: {
                code: '',
                description: '',
                discount: 0
            },
            valid: false,
            total: 0,
            finalTotal: 0,
            finalTotalAmount: 0
        };
    },
    ready: function() {
        this.getTotal();
    },
    methods: {
        getTotal: function() {
            this.$http.get('/cartTotal', function(total) {
                this.$set('total', total);
            });
        },
        validate: function() {
            this.$http.get('/coupons/' + this.coupon.code)
                .success(function(coupon, finalTotal) {
                    this.coupon = coupon;
                    this.valid = true;
                    this.coupon.description = 'Great! You will get ' + this.coupon.amount + '% off';

                    this.discountPercentage = this.coupon.amount/100;
                    this.discountAmount = this.total*(this.discountPercentage);
                    this.discountedTotal = this.total - this.discountAmount;
                    this.finalTotalAmount = Math.round(this.discountedTotal * 100);

                    this.$set('finalTotalAmount', this.finalTotalAmount);

                })
                .error(function() {
                    this.coupon.code = '';
                    this.coupon.description = 'Sorry, that coupon does not exist.';
                });
        }
    }
});

With the html as

Total: &pound; @{{ total }}

{!! Form::open(['url' => 'stripeSuccess', 'id' => 'purchase-form', 'class' => 'form-horizontal']) !!}

            <input type="hidden" name="finalTotalAmount" value="@{{ finalTotalAmount }}">

So basically the finalTotalAmount is what I pass to stripe & this needs to be either the amount from the GET request or when a discount is applied this is updated with the discount amount.

tykus's avatar

Did you even look at my rework of your Vue instance??? It works, I promise!

I have gotten rid of the total property on the Vue instance altogether. You will have two "total" amounts (i) the original total represented by finalTotal and (ii) the discounted total represented byfinalTotalAmount. When you make the getTotal()AJAX request, you must set both the finalTotal and finalTotalAmount properties. I have done this in a single line:

this.finalTotal= this.finalTotalAmount = total; // total is the amount returned by the AJAX call

At this point, you have the same amount represented by two variables. If the user enters a valid coupon, you will only update the finalTotalAmount - this is the amount which is sent to Stripe.

I have spent waaaay too long on this already... good luck with it!

theUnforgiven's avatar

Apologies I didn't see you last two replies, my bad! Thanks for helping out i'll give this a try

theUnforgiven's avatar

@tykus_ikus I have a small problem, when the total amount is over 100 it sends to stripe as 1.00 rather than 100, any idea based on what you helped me with?

theUnforgiven's avatar

It passes throught fine, just when it's over 99 it becomes an issue

tykus's avatar

O.o

Can you gist your view and vue again?

tykus's avatar

I can't see where the problem might lie in the code you have provided. What is value of @{{finalTotalAmount}} in both the Total and hidden input below:

...
    <p class="offset-m4" style="font-size: 18px;">
      Total: &pound; @{{ finalTotalAmount  }} 
      <small>(incl delivery @ &pound;3.99)</small>
    </p>
      </div>
    </div>
  </div>


  {!! Form::open(['url' => 'stripeSuccess', 'id' => 'purchase-form', 'class' => 'form-horizontal']) !!}
    <input type="hidden" name="finalTotalAmount" value="@{{ finalTotalAmount }}">
...

Whenever you post this form, how is the Stripe charge made on the server side? Do you do anything to the charge amount before making the request to Stripe?

theUnforgiven's avatar

I do nothing in the controller other than get the hidden value. So kot quite sure whats going off here. Has a test order i did for £144 was passed to stripe as £1.44

theUnforgiven's avatar

This is the method for passing to stripe.

public function createOneTimeChargeForDelivery($user, $cartItems, $userDetails, Request $request, $token)
    {
        $token = Input::get('stripe_token');
        $product = Products::where('id', Session::get('pid'))->first();

        if (Cart::total() < 75.00) {
            $money = $request->input('finalTotalAmount');
            $price = $money;
        }
        else {
            $money = $request->input('finalTotalAmount');
            $price = $money;
        }

        list($productOrdered, $colourOrdered, $sizeOrdered) = $this->orderedItems();

        $this->createOneTimeCharge($user, $userDetails, $price, $token, $productOrdered, $colourOrdered, $sizeOrdered, $money);

    }

Which then goes to the createOneTimeCharge method

public function createOneTimeCharge($user, $userDetails, $price, $productOrdered, $colourOrdered, $sizeOrdered, $money, $token)
    {
        if (Auth::check()) {
            $userEmail = Auth::user()->email;
        }
        else {
            $userEmail = str_replace(' ','', strtolower(Input::get('email')));
        }
        $token = Input::get('stripe_token');
        $user->charge($price, [
            'source' => $token,
            'receipt_email' => $userEmail,
            'metadata' => [
                'customers_name' => $userDetails['name'],
                'customers_email_address' => str_replace(' ','', strtolower(Input::get('email'))),
                'address' => $userDetails['address'],
                'address 2' => $userDetails['address2'],
                'telephone' => $userDetails['phone'],
                'city' => $userDetails['city'],
                'postcode' => $userDetails['postcode'],
                'product' => $productOrdered,
                'colour' => $colourOrdered,
                'size' => $sizeOrdered,
                'price' => $money
            ]
        ]);
    }
tykus's avatar

And you're absolutely sure that there is a difference between the charges under £100 and above???

It seems to me that you're sending an amount into the charge which you know is pounds, but Stripe thinks is pennies; your hidden field value is the same as your displayed total - remember Stripe expects the charge amount to be in pennies 144 pennies is £1.44

theUnforgiven's avatar

@tykus_ikus this has to be an issue with the JS you helped with, as anything less then £100 is fine, £100 and over shows as £1.00.

theUnforgiven's avatar

None in my controller all I'm doing is setting a variable with $request->input('finalTotalAmount') to request the total amount pass from Vue.

tykus's avatar

Dunno, can't see it other than the problem with pounds vs. pennies; you need to start to die and dump values from the point that the amount is being send to Stripe back to where it comes in from the form.

There are reasons to keep monetary values in pennies/cents throughout your application except where you display them to the user, this is just one of them

theUnforgiven's avatar

Ok, i' dont see how this can be anything else...Oh actually within the DB its set as 18.00 for example, so should I make this be

str_replace('.','', $request->input('price');

When the admins are entering prices? so it saves as pennies/cents?

theUnforgiven's avatar

Thats made no difference, I changed the DB value to a string and entered 1800 for £18.00 but it's the VUE thats doing the work here I think. as then if is it's £144.00 it shows as £1.44

theUnforgiven's avatar

The cart package I'm using shows the amount of the cart as 144.0 so I guess this could be the problem, but question is how do I convert this to the correct format

tykus's avatar

I am leaving code related comments on your Gist - as its easier to see the code as I am writing.

If you want admins to enter £ and store p in the database, you can set up a mutator in the Model. This is a magic method which will automatically change the value entered before storing it - the format of the method name must be setVariableNameAttribute($value) for an attribute called variable_name e.g. for a price attribute

  public function setPriceAttribute($value)
  {
    $this->attributes['price'] = (int) ($value * 100);
  }

Now, whenever a price is entered as (£) 144, it will be stored as 14400 (pence)

theUnforgiven's avatar

Yes I got that now.

I've kinda got it working now, but I need to do some checks to see if total amount is less then £75 to then add £3.99 delivery charge otherwise don't.

theUnforgiven's avatar

Still have the same issue, adding delivery charge to it is causing a few problems either adding too much to it or I'm doing the math wrong, not sure here, looking for a bit of help still if anyone else fancies helping out :)

Previous

Please or to participate in this conversation.