Jump to content
cleanboy

Creating a payment form with Stripe

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

Share this post


Link to post
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) {

 

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Peter Knight
      I have a few web forms which require testing on a weekly basis and I don't want the recipients (administrators) to receive these test emails.
      What would be a good way to test approx 15 forms from the front end and have the test delivered a list of secondary administrator recipients?
      I'm thinking that I could have some kind of config file which watches for a trigger word or email and then understands that it's a test and to bypass the normal admins?
      All of the forms ask for an email address so I could setup an email such as 'testform@email.not' etc which my config file (hook?) would watch for.
      Or is there a better way to do this?
      Additionally, I have a few extra requirements...
      Forms should goto an alternative success page. This is because I don't want my test to skew my Google Analytics conversion tracking Forms would need to be tested from the front-end and not the PW admin area Any advice appreciated.
      BTW I realise this should be posted in the proper FormBuilder support forum. I am in the process of renewing my license for access to that support forum.
       
    • By Rajesh Khanna
      Can anyone help with PW form to integrate with Mautic for marketing automation or that matter using Zapier to connect ?
    • By J_Szwarga
      What is the best practice for having a form (only accessible to a logged in user) save the user's progress between sections of the form? Fieldsets? Multiple forms?
      Use case: I have a very long application form that would take 30 minutes for the applicant to fill out and I would hate for all progress to be lost due to user error. The progress needs to be logged into the user's page.
      Thanks!
    • By quickjeff
      Hey guys, 
      Looking for another dev to help on multiple processwire projects as well as some other PHP apps. 
      First project is a directory style site with lots of components to come, including stripe implementation  and other fun stuff.
      Need someone ASAP to help out. 
       
      Thanks.
    • By Guy Incognito
      I'm trying to implement a front-end image upload form for user profile pictures, to a field called 'profile_picture'. The code is based on the various examples found around these forums but isn't yet 100% right.
      The form successfully submits the image to the server and updates the field in the dashboard. When you submit the firm, the page reloads and uploads the image but the template still loads the old image path (now broken path as the old image has been removed).
      I can only get the new image to show if I hit enter in the address bar forcing the page to reload.
      Any ideas? Is it a caching issue, or something to do with the order of the script? It makes no difference if I call the image after the upload form.
      <?php //Display current user image $userImg = $user->profile_picture->first(); echo '<img src="'.$userImg->url.'">'; $upload_path = $config->paths->assets . "files/avatar_uploads/"; $f = new WireUpload('userimage'); $f->setMaxFiles(1); $f->setMaxFileSize(1*1024*1024); $f->setOverwrite(true); $f->setDestinationPath($upload_path); $f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif')); if($input->post->form_submit) { if(!is_dir($upload_path)) { if(!wireMkdir($upload_path)) throw new WireException("No upload path!"); } $files = $f->execute(); if ($f->getErrors()) { foreach($files as $filename) @unlink($upload_path . $filename); foreach($f->getErrors() as $e) echo $e; } else { $user->of(false); $user->profile_picture->removeAll(); // wirearray (line added by @horst: explanation is three posts beneath) $user->profile_picture = $upload_path . $files[0]; $user->save(); $user->of(true); @unlink($upload_path . $files[0]); } } ?> <form class="forum-form" accept-charset="utf-8" action="./" method="post" enctype="multipart/form-data" > <input type="file" id="attach" name="userimage" accept="image/jpg,image/jpeg,image/gif,image/png" /> <input type="submit" name="form_submit" value="Submit"/> </form>
       
×
×
  • Create New...