0

I'm using Expresso Store 2.3.1, and trying to get take payments via the Pin Payments gateway using Pin.js. This is near identical to the Stripe.js where a card token is added to the form then the token is posted to the server no the card details.

I have everything working right upto the point that I post the form with the card token to Store. I've demonstrated it working upto this point by disabling the post of the form to the server. When I do this you can clearly see the card token in the hidden input.

But when it posts to the server, I don't get any errors in the console or on the page, and I'm returned to the same checkout page.

I've removed inline errors in case there were any hidden errors I was missing.

Here's the relevant javascript:

    // 1. Wait for the page to load
$(function() {

  // 2. Create an API object with your publishable api key, and
  // specifying 'test' or 'live'.
  //
  // Be sure to use your live publishable key with the live api, and
  // your test publishable key with the test api.
  var pinApi = new Pin.Api('pk_tlFaC6Qk0MDp4wdNsa-noQ', 'test');

  var form = $('form.pin'),
      submitButton = form.find(":submit"),
      errorContainer = form.find('.errors'),
      errorList = errorContainer.find('ul'),
      errorHeading = errorContainer.find('h3');

  // 3. Add a submit handler to the form which calls Pin.js to
  // retrieve a card token, and then add that token to the form and
  // submit the form to your server.
  form.submit(function(e) {
    e.preventDefault();

    // Clear previous errors
    errorList.empty();
    errorHeading.empty();
    errorContainer.hide();

    // Disable the submit button to prevent multiple clicks
    submitButton.attr({disabled: true});

    // Fetch details required for the createToken call to Pin Payments
    var card = {
      number:           $('#cc-number').val(),
      name:             $('#cc-name').val(),
      expiry_month:     $('#cc-expiry-month').val(),
      expiry_year:      $('#cc-expiry-year').val(),
      cvc:              $('#cc-cvc').val(),
      address_line1:    $('#address-line1').val(),
      address_line2:    $('#address-line2').val(),
      address_city:     $('#address-city').val(),
      address_state:    $('#address-state').val(),
      address_postcode: $('#address-postcode').val(),
      address_country:  $('#address-country').val()

    };

    // Request a token for the card from Pin Payments
    pinApi.createCardToken(card).then(handleSuccess, handleError).done();
  });

  function handleSuccess(card) {
    // Add the card token to the form
    //
    // Once you have the card token on your server you can use your
    // private key and Charges API to charge the credit card.
    $('<input>')
      .attr({type: 'hidden', id: 'payment_token', name: 'payment[token]'})

      .val(card.token)
      .appendTo(form);
      console.log(card.token);

    // Resubmit the form to the server
    //
    // Only the card_token will be submitted to your server. The
    // browser ignores the original form inputs because they don't
    // have their 'name' attribute set.
    form.get(0).submit();
  }

  function handleError(response) {
    errorHeading.text(response.error_description);

    if (response.messages) {
      $.each(response.messages, function(index, paramError) {
        $('<li>')
          .text(paramError.param + ": " + paramError.message)
          .appendTo(errorList);
      });
    }

    errorContainer.show();

    // Re-enable the submit button
    submitButton.removeAttr('disabled');
  };
});

And here's my checkout code:

      {exp:store:checkout
                form_class="form-horizontal pure-form pure-form-stacked pin"
                return="checkout/order/ORDER_HASH"
                payment_method="Pin"
               }

                {if no_items}
                    {redirect="checkout"}
                {/if}

                <input type="hidden" id="address_line-1" value="{billing_address1}" />
                <input type="hidden" id="address_line-2" name="{billing_address2}" />
                <input type="hidden" id="address_city" name="{billing_city}" />
                <input type="hidden" id="address_state" name="{billing_state}" />
                <input type="hidden" id="address_postcode" name="{billing_postcode}" />
                <input type="hidden" id="address_country" name="{billing_country}" />
            <div class="pure-g">
                <div class="pure-u-1 pure-u-md-1-2">

            <fieldset>
                <legend>Payment</legend>

                {if error:payment_method}
                    <div class="pure-control-group">
                        <div class="alert alert-error">{error:payment_method}</div>
                    </div>
                {/if}

                <div class='errors'>
                    <h3></h3>
                    <ul></ul>
                </div>

                <div class="pure-control-group">
                    <label class="control-label" for="cc-number">Card No</label>
                    <div class="controls">
                        <input type="text" id="cc-number" value="4200000000000000" />
                    </div>
                </div>

                <div class="pure-control-group">
                    <label class="control-label" for="cc-name">Name</label>
                    <div class="controls">
                        <input type="text" id="cc-name"  value="TEST USER" />
                    </div>
                </div>

                <div class="ontrol-group">
                    <label class="control-label" for="cc-expiry-month">Expiry</label>
                    <div class="controls">
                        <select id="cc-expiry-month" style="width:auto">
                            <option value="01">01</option>
                            {exp_month_options}
                        </select>
                        <select id="cc-expiry-year"  style="width:auto">
                            <option value="2016">2016</option>
                            {exp_year_options}
                        </select>
                    </div>
                </div>

                <div class="pure-control-group">
                    <label class="control-label" for="cc-cvc">CSC</label>
                    <div class="controls">
                        <input type="text" id="cc-cvc" value="123" class="input-mini" />
                    </div>
                </div>

            </fieldset>
        </div>

    </div>
    <div class="form-actions">
        <a href="{path='checkout/step2'}" class="pure-button">Back</a>
        <input type="submit" name="btnSubmit" value="Place Order" class="pure-button pure-button-primary" />
    </div>

{/exp:store:checkout}

This one really has be beat. While I think I might be seeing an issue where Pin.js intercepts every form submission and get's a new token for every form submission, if I disable Pin.js and manually add the token that was previously issued, it still won't give a meaningful response after post.

1

I managed to get this working but only after two hours of debugging.

  • Pin.net requires address-line1, address-city
  • Using form.get(0).submit() will not work if you submit button has name="submit"
  • Store redirects you to the same page if no POST['submit'] is found. Your submit button must be named "submit" e.g. name="submit"
  • If you disable (disabled="disabled") your submit button it will not be "POSTED", so Store will redirect you to the same page

So instead of using form.submit() i used a click handler on the submit button.

Here is my example code

<html>
<head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src='https://cdn.pin.net.au/pin.v2.js'></script>

    <script>
    // 1. Wait for the page to load
    $(function() {

        // 2. Create an API object with your publishable api key, and
        // specifying 'test' or 'live'.
        //
        // Be sure to use your live publishable key with the live api, and
        // your test publishable key with the test api.
        var pinApi = new Pin.Api('pk_kmzTxiNh25A6lU1sZIgmqQ', 'test');

        var form = $('form.pin');
        var submitButton = form.find(":submit");
        var errorContainer = form.find('.errors');
        var errorList = errorContainer.find('ul');
        var errorHeading = errorContainer.find('h3');

        // 3. Add a submit handler to the form which calls Pin.js to
        // retrieve a card token, and then add that token to the form and
        // submit the form to your server.
        submitButton.click(function(e) {
            e.preventDefault();

            // Clear previous errors
            errorList.empty();
            errorHeading.empty();
            errorContainer.hide();

            // Disable the submit button to prevent multiple clicks
            submitButton.attr({disabled: true});

            // Fetch details required for the createToken call to Pin Payments
            var card = {
                number:           $('#payment_card_no').val(),
                name:             $('#payment_name').val(),
                expiry_month:     $('#payment_exp_month').val(),
                expiry_year:      $('#payment_exp_year').val(),
                cvc:              $('#payment_csc').val(),
                address_line1:    $('#address-line1').val(),
                address_line2:    $('#address-line2').val(),
                address_city:     $('#address-city').val(),
                address_state:    $('#address-state').val(),
                address_postcode: $('#address-postcode').val(),
                address_country:  $('#address-country').val()
            };

            // Request a token for the card from Pin Payments
            pinApi.createCardToken(card).then(handleSuccess, handleError).done();

            return false;
        });

        function handleSuccess(card) {
            // Add the card token to the form
            //
            // Once you have the card token on your server you can use your
            // private key and Charges API to charge the credit card.
            $('<input>')
                .attr({type: 'hidden', name: 'payment[token]'})
                .addClass('card_token')
                .val(card.token)
                .appendTo(form);

            // Turn off the click handler and click it
            submitButton.off('click').val('processing...').removeAttr('disabled').click();
        }

        function handleError(response) {
            // Empty the card token
            form.find('.card_token').val('');

            errorHeading.text(response.error_description);

            if (response.messages) {
                $.each(response.messages, function(index, paramError) {
                    $('<li>')
                        .text(paramError.param + ": " + paramError.message)
                        .appendTo(errorList);
                });
            }

            errorContainer.show();

            // Re-enable the submit button
            submitButton.removeAttr('disabled');
        }
    });
    </script>
</head>
<body>


{exp:store:checkout
    form_class="form-horizontal pure-form pure-form-stacked pin"
    return="store_example/order/ORDER_HASH"
    error_handling="inline"
    payment_method="Pin"
}

    {if no_items}
        {redirect="store_example/checkout"}
    {/if}

    <div class='errors' style='display:none'>
        <h3></h3>
        <ul></ul>
    </div>

    <input type="hidden" id="address_line-1" value="{billing_address1}">
    <input type="hidden" id="address_line-2" value="{billing_address2}">
    <input type="hidden" id="address_city" value="{billing_city}">
    <input type="hidden" id="address_state" value="{billing_state}">
    <input type="hidden" id="address_postcode" value="{billing_postcode}">
    <input type="hidden" id="address_country" value="{billing_country}">

    <fieldset>
        <legend>Payment</legend>

        <label>Card No</label>
        <input type="text" id="payment_card_no" value="">

        <label>Name</label>
        <input type="text" id="payment_name" value="">

        <label>Expiry</label>
        <select id="payment_exp_month" style="width:auto">
            <option value=""></option>
            {exp_month_options}
        </select>
        <select id="payment_exp_year" style="width:auto">
            <option value=""></option>
            {exp_year_options}
        </select>

        <label>CSC</label>
        <input type="text" id="payment_csc" value="" class="input-mini">
    </fieldset>

    <input type="submit" name="submit" value="Place Order">

{/exp:store:checkout}

</body>
</html>
| improve this answer | |
  • Thanks Manuel, I'll need to pickup this project to test again in a few days, makes sense though. I had already figured out the submit button name but didn't work when I tested it, now I know it didn't work as I was missing half the solution. Thanks for your help on this, I'll post back and accept once I've tested it all. – neekster Sep 1 '14 at 15:51
  • Took me ages to come back to this one, I've now tested and working perfectly. Thanks again. – neekster Oct 26 '14 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.