0

i have an Angular Storefront app set up. I have a shopping cart functionality in place and a stripe "pay with card" button etc. pretty much looks like this:

<form action="/#/order" method="POST">

        <script
          src="https://checkout.stripe.com/checkout.js" class="stripe-button"
          data-key="{{ stripeApiKey }}"
          data-billingAddress=true
          data-shippingAddres=true
          data-amount="{{ amount }}"
          data-name="StoreFront Name"
          data-description="Custom-Made Jewellery"
          data-image="../images/www/logo.png"
          data-locale="auto">
        </script>
      </form>

Evrything up to this point is working fine. I submit the form and stripe returns the token but the form goes to the server following the route localhost/order (without the # symbol) instead of angular's localhost/#/order.

  1. Why is stripe forcing this redirect? In other words why isn't angular capturing this return call?

Anyways. Then I create a route with Laravel to capture this and dump to inspect the returned data like so:

Route::post('/order', function($request){ dd($request); });

Yep, data captured by stripe-generated form is returned except amount is missing... I mean everything including stripeToken, buyer's details such as: Name, Email, Billing and Shipping address are returned BUT detail regarding the amount is missing.

  1. Is this normal or I'm I missing something?

  2. Lastly currency is still showing the default: Where can I change currency from say USD to GBP?

Thanks in advance

3 Answers3

1

1/ I don't think Checkout is forcing the redirect, but I don't know enough about Angular to explain what's going on, sorry.

2/ Yes, this is normal. The amount passed to Checkout in the data-amount configuration option is used for display purposes only. The actual amount that is charged is the one you pass in the amount parameter in the charge creation request in your server-side code.

If you need the amount to be user-specified (for instance, if you're taking donations), you'll need to add the amount to the form. Here is a simple JSFiddle to illustrate this case: https://jsfiddle.net/ywain/g2ufa8xr/

3/ You can use the data-currency parameter to change the currency displayed in the Checkout form. Just like data-amount, this is for display purposes only and the actual currency used for the charge is specified by the currency parameter in the charge creation.

Ywain
  • 16,854
  • 4
  • 51
  • 67
  • Thanks @Ywain. I'm still investigating about the redirect but meanwhile I'm just gonna handle it on the server. Spot on the the `data-currency` parameter - very straight forward. And thanks for the fiddle ... helped clear some things out. – user3881779 Oct 15 '15 at 18:22
  • Glad I could help! If you want, you can also visit us on our IRC channel #stripe on freenode. Stripe engineers and enthusiasts often hang out there and will be happy to help with your integration questions! – Ywain Oct 16 '15 at 13:06
0

Stripe doesn't get involved with your form aside from preventing the default action on form submit event and stopping event propagation. Once the checkout process completes, it appends the relevant data to your form and then triggers a form submit event that is handled by HTML / Javascript natively.

I recommend using something like https://github.com/tobyn/angular-stripe-checkout to get your Stripe response handled correctly by Angular.

Otherwise you could add ng-submit="handleStripeCheckout($event)" to your form instead of action="/#/form". When Stripe's checkout process completes, your $scope.handleStripeCheckout method will be run and you can analyze the new form data inside that method.

Edit: Stripe checkout.js actually triggers form.submit(). That's a pretty bad bug on their part considering that almost no browsers handle that correctly. (Form submitted using submit() from a link cannot be caught by onsubmit handler)

Community
  • 1
  • 1
David Boskovic
  • 1,469
  • 9
  • 14
  • I think you have a point there. just curious, so in cases where its a custom form where would the form data itself be collected? inside `handleStripeCheckout($event)` ? And how would that (the handler) look like? – iTenzo Oct 19 '15 at 14:38
  • You'd need to grab your form elements from the form using either javascript or jQuery since those fields wouldn't be available in scope. `$($event.target).serializeArray()` Super hacky approach. However, in doing some testing, I discovered that for some reason, Stripe is actually calling `form.submit()` which bypasses the submit event and makes my answer invalid. – David Boskovic Oct 19 '15 at 16:37
0

This is what i managed to do.

I went with the custom form approach. I had a form template to capture both customer and card inputs in billing.template.html like so:

<form method="POST" id="payment-form">
  <span class="payment-errors"></span>

  <div>
    <label>Name</label>
    <input type="text" name="name" data-stripe="name">
  </div>

  <div>
    <label>Email</label>
    <input type="text" name="email" data-stripe="address_email">
  </div>

  <div>
    <label>Address Line 1</label>
    <input type="text" name="street" data-stripe="address_line1">
  </div>

  <div>
    <label>Postcode</label>
    <input type="text" name="postcode" data-stripe="address_zip">
  </div>

  <div>
    <label for="country">Country</label>
      <select ng-include="'../templates/_partials/_countrylist.html'"
        id="countries" name="country" class="form-control"
        name="country" ng-model="country" id="country" size="2"
        data-stripe="address_country" required></select>
  </div>

  <div class="form-row">
    <label>
      <span>Card Number</span>
      <input type="text" name="cardNumber" size="20" data-stripe="number"/>
    </label>
  </div>

  <div class="form-row">
    <label>
      <span>CVC</span>
      <input type="text" name="cvc" size="4" data-stripe="cvc"/>
    </label>
  </div>

  <div class="form-row">
    <label>
      <span>Expiration (MM/YYYY)</span>
      <input type="text" name="expMonth" size="2" data-stripe="exp-month"/>
    </label>
    <span> / </span>
    <input type="text" name="expYear" size="4" data-stripe="exp-year"/>
  </div>

  <button id="customButton">Pay with Card</button>
</form>

I know we are not supposed to use name attribute in those form inputs but i left them so i could use angular validation, but i remove them using jquery before submitting to server.

Now i created a controller to handle the form: BillingController.js. In there i had an "on click" handler which kick started things by getting a hold of the form and doing some preparatory work: disabling button to prevent further clicks and removing those 'dreaded' name attributes, comme ca:

  $('#customButton').on('click',function(event) {
    var $form = $('#payment-form');

    // Disable the submit button to prevent repeated clicks
    $form.find('button').prop('disabled', true);

    //NOW REMOVE THOSE NAME ATTRIBUTES
    $form.find('input').removeAttr('name');

    // call Stripe object and send form data to get back the token.
    // NOTE first argument is $form 
    Stripe.card.createToken($form, stripeResponseHandler);

    // Prevent the form from submitting with the default action
    return false;
  });

Now let me quote the documentation here as this is very important to understand: https://stripe.com/docs/tutorials/forms

The important code to notice is the call to Stripe.card.createToken. The first argument is the form element containing credit card data entered by the user. The relevant values are fetched from their associated inputs using the data-stripe attribute specified in the form.

Next we create stripeResponseHandler(). Remember it was the second argument in Stripe.card.createToken($form, stripeResponseHandler); above which gets called when Stripe returns the token.

function stripeResponseHandler(status, response) {
  var $form = $('#payment-form');

  if (response.error) {
    // Show the errors on the form
    $form.find('.payment-errors').text(response.error.message);
    $form.find('button').prop('disabled', false);
  } else {
    // response contains id and card, which contains additional card details
    var token = response.id;
    // Insert the token into the form so it gets submitted to the server
    $form.append($('<input type="hidden" name="stripeToken" />').val(token));
    // and submit
    $form.get(0).submit();
  }
};

This is copy and paste stuff from stripe's own documentation: https://stripe.com/docs/tutorials/forms. Now, I want to say that, this is where a lot of us were tripping over the fact that form was performing a redirect etc. - notice final line $form.get(0).submit(); . Thats what caused the auto submit, redirecting to what ever action was on form, if u had any (in my case action attribute wasn't necessary as i was doing redirects in my controller).

So i decided to remove $form.get(0).submit() and implemented my own redirect after i was done sending data to the server.

NOTE: Stripe's response will have included data from the $form - try console.log(response); to have an idea of what's being posted back.

FINALLY: We check if there were any errors returned and if so display them. Otherwise its all good, send data to the server.

The final code looks like:

function stripeResponseHandler(status, response) {

      var $form = $('payment-form');

      if (response.error) {
        // Show the errors on the form
        $form.find('.payment-errors').text(response.error.message);
      } else {
        // response contains id and card, which contains additional card details
        var token = response.id;

        // prepare data
        var data = {
            stripeToken: token,
            fullName: response.card.name,
            street: response.card.address_line1,
            postcode: response.card.address_zip,
            town: response.card.address_city,
            country: response.card.address_country,
            last4: response.card.last4
        };

        // send to server
        $http.post('/checkout', data).then(function(result){
          // here you can redirect yourself.
          window.location.href = "/#/order-complete";
        });

      }
    };

Angular really playing well with stripe here. Check out this link also: https://gist.github.com/boucher/1750368 - learn a lot from it.

I hope it helps someone today. Happy coding!

iTenzo
  • 134
  • 1
  • 3
  • 11