Jump to content

Creating a payment form with Stripe


cleanboy
 Share

Recommended Posts

Hey all,

Mods; please feel free to move this if it is in the incorrect place although I thought this was the best place for it.

Kinda new to processwire but getting the hang of it....I am trying to build a payment form with the Stripe platform but failing miserably....I really hope someone can help!

So I am building my form with the PW API and also following through this documentation here: https://stripe.com/docs/elements and here: https://stripe.com/docs/charges

My form is pretty basic just for testing purposes right now:

<form id="payment-form" class="pusher" name="payment-form" method="post" action="./#booking-form">
			<?php
			$tokenName = $this->session->CSRF->getTokenName();
			$tokenValue = $this->session->CSRF->getTokenValue();
			echo '<input type="hidden" id="_post_token" name="' . $tokenName . '" value="' . $tokenValue . '"/>';

			$out = "";

			$out .= '<div id="card-number"></div>
			<div id="card-errors"></div>';

			// create a new form field (also field wrapper)
			$form = $modules->get("InputfieldForm");
			$form->action = "./";
			$form->method = "post";
			$form->attr("id+name",'payment-form');

			// First Name
			$field = $modules->get("InputfieldText");
			$field->skipLabel = true;
			$field->attr('id+name','name');
			$field->attr('placeholder','Full Name');
			$field->required = 1;
			$form->append($field); // append the field to the form

			// create email field
			$field = $modules->get("InputfieldEmail");
			$field->attr('id+name','email');
			$field->attr('placeholder','Email');
			$field->required = 1;
			$form->append($field); // append the field

			// oh a submit button!
			$submit = $modules->get("InputfieldSubmit");
			$submit->attr("value","SUBMIT");
			$submit->attr("id+name","submit");
			$submit->attr("class","cta white");
			$form->append($submit);

			if($input->post->submit) {

				// user submitted the form, process it and check for errors
				$form->processInput($input->post);

				if($form->getErrors()) {
					// the form is processed and populated
					// but contains errors
					$out .= $form->render();
				} else {
					foreach($input->post as $key => $value)
    					echo htmlentities("$key = $value") . "<br />";
				}
			} else {
				foreach($form->children as $input){
					$out .= "<div class='col-sm-6'>{$input->render()}</div>";
				}
			}
			echo $out;
			?>

		</form>

That alone works fine and returns all of the fields values after I click submit which is great...now we add the javascript:

//Stripe
	var stripe = Stripe('pk_test_*****************');
	var elements = stripe.elements();

	// Custom styling can be passed to options when creating an Element.
	var style = {
	  base: {
	    // Add your base input styles here. For example:
	    fontSize: '16px',
	    lineHeight: '24px'
	  }
	};

	// Create an instance of the card Element
	var card = elements.create('card', {style: style});

	// Add an instance of the card Element into the `card-element` <div>
	card.mount('#card-number');

	card.addEventListener('change', function(event) {
	  	var displayError = document.getElementById('card-errors');
	  	if (event.error) {
	    	displayError.textContent = event.error.message;
	  	} else {
	    	displayError.textContent = '';
	  	}
	});

	// Create a token or display an error when the form is submitted.
	var form = document.getElementById('payment-form');
	form.addEventListener('submit', function(event) {
	  	event.preventDefault();

	  	stripe.createToken(card).then(function(result) {
	    	if (result.error) {
		      	// Inform the user if there was an error
		      	var errorElement = document.getElementById('card-errors');
		      	errorElement.textContent = result.error.message;
	    	} else {
	      		// Send the token to your server
	      		stripeTokenHandler(result.token);
	    	}
	  	});
	});

	function stripeTokenHandler(token) {
		// Insert the token ID into the form so it gets submitted to the server
		var form = document.getElementById('payment-form');
		var hiddenInput = document.createElement('input');
		hiddenInput.setAttribute('type', 'hidden');
		hiddenInput.setAttribute('name', 'stripeToken');
		hiddenInput.setAttribute('value', token.id);
		form.appendChild(hiddenInput);

		// Submit the form
		form.submit();
	}

This gives an error in the console: Uncaught (in promise) TypeError: form.submit is not a function. 

This is because our submit input has the name "submit". Ok thats cool...I can change that but once I do the page essentially refreshes and the form just shows again and it doesnt give me the values it did before....I guess it isnt posting? either way after I submit the form after changing the name of the submit button the form just loads again and anything inside: if($input->post->submit) doesnt seem to execute...so what gives?

If anybody here has done this before with PW 3+ I would really love an example of how to use this to the point of sending information to Stripe.

I have installed Stripe with composer and it would appear that I can refer to it in Processwire just fine...my problem is just getting to the point where I need to refer to it which is after the submit.

I have seen the PaymentStripe module but tbh the documentation on that is somewhat lacking and im not even sure it works with the latest version of stripe? Im also not sure how to integrate that into a form either unless you use something along the lines of the example code provided after the form has been submitted but I cant even seem to submit the form to even try that and im not really sure why.

Here is the PaymentStripe module page for those wondering: https://modules.processwire.com/modules/payment-stripe/

Any help would be greatly appreciated!

Cleanboy

Link to comment
Share on other sites

45 minutes ago, cleanboy said:

This is because our submit input has the name "submit". Ok thats cool...I can change that but once I do the page essentially refreshes and the form just shows again and it doesnt give me the values it did before....I guess it isnt posting?

Change this

// oh a submit button!
$submit = $modules->get("InputfieldSubmit");
$submit->attr("value","SUBMIT");
$submit->attr("id+name","submit");
$submit->attr("class","cta white");
$form->append($submit);

if($input->post->submit) {

into

// oh a submit button!
$submit = $modules->get("InputfieldSubmit");
$submit->attr("value","1"); // any nonempty value would work!
$submit->attr("id+name","submitForm"); // change `name` attribute to something else to prevent name clash with form.submit
$submit->attr("class","cta white");
$form->append($submit);

if($input->post->submitForm) {

 

Link to comment
Share on other sites

Hey, thanks for the reply.

As I mentioned before, although maybe not too clearly, is that I have tried changing the button id+name and the form does submit but processwire doesnt process it. The page essentially refreshes and i just see the form fields again instead of outputting the result of 

if($input->post->submit) 

which should be 

foreach($input->post as $key => $value)
	echo htmlentities("$key = $value") . "<br />";

That if statement only seems to work as true when I name the button "submit" which i cant do because it overrides the javascript.

Link to comment
Share on other sites

17 minutes ago, cleanboy said:

That if statement only seems to work as true when I name the button "submit" which i cant do because it overrides the javascript.

if($input->post->$fieldName) works when there's a field named $fieldName inside the form and its `value` attribute evaluates to a truthy value. So when you change id+name of the`submit` button to `submitForm`, for instance, if ($input->post->submitForm) will work too.

Link to comment
Share on other sites

OOOOOohhhhhh! I did not notice the change of 

if($input->post->submitForm)

You suggested...

although I tried that and it didnt work either :(

Trying to limit the code as much as possible...this is what I have now:

<form id="payment-form" class="pusher" name="payment-form" method="post" action="./">
			<?php
			$tokenName = $this->session->CSRF->getTokenName();
			$tokenValue = $this->session->CSRF->getTokenValue();
			echo '<input type="hidden" id="_post_token" name="' . $tokenName . '" value="' . $tokenValue . '"/>';

			$out = "";

			$out .= '<div id="card-number"></div>
			<div id="card-errors"></div>';

			// create a new form field (also field wrapper)
			$form = $modules->get("InputfieldForm");
			$form->action = "./";
			$form->method = "post";
			$form->attr("id+name",'payment-form');

			// First Name
			$field = $modules->get("InputfieldText");
			$field->skipLabel = true;
			$field->attr('id+name','name');
			$field->attr('placeholder','Full Name');
			$field->required = 1;
			$form->append($field); // append the field to the form

			// create email field
			$field = $modules->get("InputfieldEmail");
			$field->attr('id+name','email');
			$field->attr('placeholder','Email');
			$field->required = 1;
			$form->append($field); // append the field

			// oh a submit button!
			$submit = $modules->get("InputfieldSubmit");
			$submit->attr("value","Submit Form"); // any nonempty value would work!
			$submit->attr("id+name","submitForm");
			$submit->attr("class","cta white");
			$form->append($submit);

			if($input->post->submitForm) {

				// user submitted the form, process it and check for errors
				$form->processInput($input->post);
              
					foreach($input->post as $key => $value)
    					echo htmlentities("$key = $value") . "<br />";
              
			} else {
				// render out form without processing
				//$out .= $form->render();
				foreach($form->children as $input){
					$out .= "<div class='col-sm-12'>{$input->render()}</div>";
				}
			}
			echo $out;
			?>

		</form>

It still just basically refreshes...well I cant tell what its doing...I dont really know how to debug this more effectively and actually see what is happening.

Link to comment
Share on other sites

1. You have two forms nested inside each other.
2. Here's a revised and simplified version:

<?php namespace ProcessWire;

// create a new form field (also field wrapper)
/** @var $form InputfieldForm */
$form = $modules->InputfieldForm;
$form->action = "./";
$form->attr("id+name", 'payment-form');
$form->addClass('pusher');
// $form->method = "post"; // default value

// add a single field quickly
$form->add([
    'type'        => 'text', // type is Inputfield<Type> without `Inputfield`
    'id+name'     => 'name',
    'placeholder' => 'Full Name',
    'required'    => 1,
]);

// add multiple fields quickly
$form->importArray([
    [
        'type' => 'email',
        'id+name' => 'email',
        'placeholder' => 'Email',
        'required' => 1,
    ],
    [
        'type' => 'submit',
        'id+name' => 'submitForm',
        'class' => 'cta white',
        'value' => 'Submit Form', // apparently value becomes the button label
    ],
    [
        'type' => 'markup', // add custom markup
        'value' => '<div id="card-number"></div>
                    <div id="card-errors"></div>'
    ]
]);

// CSRF token field is added by default


// Process input
if ($input->post->submitForm) {
    $form->processInput($input->post);
    if (!count($form->getErrors())) {
        // process credit card
    } else {
        // form has errors, append errors to the form
        $errors = join('<br>', $form->getErrors());
        $form->add([
            'type' => 'markup',
            'value' => '<div id="form-errors">$errors</div>'
        ]);
        echo $form->render();
    }
} else {
    // not posted, show the form
    echo $form->render();
}

 

  • Like 1
Link to comment
Share on other sites

Hey,

I got rid of the whole form I had and just copied that exactly and it still just refreshes when I click submit. The only difference being I was already declaring namespace ProcessWire at the top of the page before anything else but I dont think that is the problem.

I tried creating a separate php file and placing it in a folder I created called /site/php/ but the code did not execute from there....not even an echo "test" works.

Thanks for the help by the way...I really appreciate it.

Link to comment
Share on other sites

Here's the simplified version of your JS.

One thing you should look out for is that inside your form submit handler, you were preventing default submit action, but then submitting again without removing the handler, which caused the same routine to run repeatedly (submit? can't -> validate -> submit -> can't ,,, repeat).

When the form validates and you're ready to submit, remove the handler (that prevents the default action), so that the form can submit properly.

// Stripe API
var stripe = Stripe('pk_test_*****************');
var elements = stripe.elements();
// DOM Elements
var form = document.getElementById('payment-form');
var displayError = document.getElementById('card-errors');

// Card style
var style = {
    base: {
        fontSize: '16px',
        lineHeight: '24px'
    }
};

// Create an instance of the card Element
// Add an instance of the card Element into the `card-element` <div>
var card = elements.create('card', {style: style});
card.mount('#card-number');

card.addEventListener('change', function (event) {
    if (event.error) showError(event.error.message);
    else showError(); // remove previous error
});

form.addEventListener('submit', handleSubmit);

function handleSubmit(event) {
    event.preventDefault();

    stripe.createToken(card).then(function (result) {
        if (result.error) {
            return showError(result.error.message);
        }

        // remove previous errors
        showError();

        // Send the token to your server
        injectToken(result.token.id);

        // prevent infinite loop
        form.removeEventListener('submit', handleSubmit);

        // send the form
        form.submit();
    });
}

function injectToken(tokenId) {
    // Insert the token ID into the form so it gets submitted to the server
    var tokenInput = document.createElement('input');
    tokenInput.type = 'hidden';
    tokenInput.name = 'stripeToken';
    tokenInput.value = tokenId;

    form.appendChild(tokenInput);
}

function showError(errorMessage) {
    // if error is not given, remove previous error
    errorMessage = errorMessage || '';
    displayError.textContent = errorMessage;
}

 

  • Like 3
Link to comment
Share on other sites

Ok, this was a very weird problem.

It turns out that when submitting a form with JS, value of the submit button isn't posted with the form, that's why if ($input->post->submitForm) wasn't working as it would normally. We've solved the problem by checking for another field, `stripeToken`, to ensure that the form has been posted properly before processing the credit card.

https://stackoverflow.com/questions/1709301/javascript-submit-does-not-include-submit-button-value

Another way to solve this problem would be listening to submit button click instead of form submit, then preventing default action, validating the form with Stripe, and actually triggering click on the button.

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...