Closed stephenturvey closed 3 years ago
Hi There,
If you could post your perch_stripe_checkout.js file that should be enough to get to the bottom of your issues
I think this is an error in your JS implementation for stripe elements. When calling the Stripe SDK you need to supply your publishable key.
var stripe = new Stripe('pk_test_xxxxx');
From the error in the console I guess you are calling it without the key.
As for the second error, are you loading the JS file directly or are you using a bundler such as webpack? If you are using the JS example file it requires a bundler for it to work. As a few others have mentioned they are struggling with the JS as they aren't using bundlers, I'll stick another example up that you can just drop in.
Working on the publishable key being pulled in from the wrong config now as well.
Hi, thanks for getting back to me.
On my checkout page I'm just linking to the js file that was included in the download like so:
<script src="/js/perch_stripe_checkout.js"></script>
I haven't edited that file at all so it is as provided (didn't see anything in readme about editing this). Not using a bundler so guess that's where it's going wrong?
I searched all the code for your example above:
var stripe = new Stripe('pk_test_xxxxx');
But the closest I could find was within stripe_intents_payment_form.html:
var stripe = Stripe('<perch:shop id="publishable_key" escape="true">');
The two pull requests should help address your above issues. The correct gateway config should be pulled in correctly now.
I've added a new JS example that should work in the browser without needing to be changed. The file creates a function you can call anywhere once it's included, and the function call is included at the bottom of the example, which you'll need to remove if you want to call it elsewhere. You may want to update/edit the createPayment method to include/remove any fields to be passed to stripe (billing details e.t.c) or to add extra error handling.
Hi Byron,
Well the good news is that the correct gateway config is now pulled in.
Unfortunately I've still got no luck with anything else, I don't know much about js at all so I suspect I've got a mish-mash of my old code, this new code and the stuff I found on the other issues pages, not knowing what is what. I'll paste my code below and hopefully it's something simple I've missed:
<form id="payment-form" action="/checkout" method="post">
<div id="card-element"></div>
<!-- Hidden fields used to send to stripe checkout -->
<input type="hidden" value="<perch:member id="email" escape="true" />" name="email"/>
<input type="hidden" value="<perch:member id="first_name" escape="true" /> <perch:member id="last_name" escape="true" />" name="cardholderName"/>
<!-- Address Fields if passed through -->
<input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
<!-- ./Address Fields if passed through -->
<input type="hidden" id="paymentMethodID" name="paymentMethodID"/>
<!-- ./Hidden fields used to send to stripe checkout -->
<!-- Regular button as we want to submit with JS -->
<button type="button" class="button button--primary">Make Payment</button>
</form>
<script src="https://js.stripe.com/v3/"></script>
<!-- copied from https://github.com/RootStudio/Root-Perch-Shop-Omnipay-v3/issues/4 -->
<script>
var stripe = Stripe('<perch:shop id="publishable_key" escape="true">');
var elements = stripe.elements();
var card = elements.create('card', {
hidePostalCode: true
});
card.mount('#card-element');
// The form is submitted
var form = document.getElementById('payment-form');
var cardholderName = document.querySelector( '[name="cardholderName"]' );
var email = document.querySelector( '[name="email"]' );
var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );
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);
stripe.createPaymentMethod({
type: 'card',
card: card,
billing_details: {
name: cardholderName.value,
email: email.value,
},
})
.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
paymentMethodID.value = result.paymentMethod.id;
//console.log(result.paymentMethod);
form.submit();
}
});
}
function createToken() {
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);
}
});
};
// Create a token when the form is submitted.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
createToken();
});
</script>
<perch:showall>
//Check if stripe has been loaded
if ( typeof Stripe != 'undefined' ) {
var stripe = Stripe( key );
var elements = this.stripe.elements();
}
// Get the card element container
if ( document.querySelector( '#card-element' ) ) {
// Create stripe card elements
var cardElement = this.elements.create( 'card', {
hidePostalCode: true
} );
cardElement.mount( '#card-element' );
// Get hidden fields for validation
var cardholderName = document.querySelector( '[name="cardholderName"]' );
var email = document.querySelector( '[name="email"]' );
var address = document.querySelector( '[name="address"]' );
var city = document.querySelector( '[name="city"]' );
var postcode = document.querySelector( '[name="postcode"]' );
var cardButton = document.getElementById( 'card-button' );
var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );
var form = document.querySelector( '#stripeForm' );
// Add listener for the form button
cardButton.addEventListener( 'click', function( ev ) {
// Get payment intent from stripe
stripe.createPaymentMethod( 'card', this.cardElement, {
billing_details: {
name: cardholderName.value,
email: email.value,
address: {
line1: address.value,
postal_code: postcode.value,
city: city.value,
country: 'GB'
}
}
} ).then( function( result ) {
paymentMethodID.value = result.paymentMethod.id;
// Submit form for next step
form.submit();
} );
} );
}
}
// Call function with
PerchStripeCheckout('pk_test_xxxxxx');
include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');
// hide the address/payment options with css
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
$status = 'loggedin';
// pass to stripe basics
$customer_firstname = perch_member_get('first_name');
$customer_surname = perch_member_get('last_name');
$customer_name = $customer_firstname . ' ' . $customer_surname;
$customer_email = perch_member_get('email');
// pass to stripe cart titles, skus
$cart = perch_shop_cart(['skip-template'=>true]);
$cart_items = $cart['items'];
foreach($cart_items as $item) {
$product_titles[] = $item['title'];
$product_skus[] = $item['sku'];
}
$product_title = implode(', ', $product_titles);
$product_sku = implode(', ', $product_skus);
}
else {
$status = 'loggedout';
}
// stripe urls
if (perch_member_logged_in() && perch_post('stripeToken')) {
$return_url = '/pay/success';
$cancel_url = '/pay/error';
// stripe data
perch_shop_checkout('stripe_intents', [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
//'token' => perch_post('stripeToken'),
'payment_method' => perch_post('paymentMethodID'), // Pass the payment method to the gateway
'receipt_email' => $customer_email,
'metadata' => [
'email' => $customer_email,
'name' => $customer_name,
'product(s)' => $product_title,
'sku(s)' => $product_sku,
],
'confirm' => true, // Tell the gateway to confirm the gateway
]);
}
// paypal urls
if (perch_member_logged_in() && isset($_POST["paypal"])) {
$return_url = 'http://esl.local/pay/result-paypal';
$cancel_url = 'http://esl.local/pay/error';
perch_shop_checkout('paypal-express', [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
]);
}
perch_layout('header', array(
'title' => "Confirm your order",
));
?>
<div class="clear feature-block ta-c mb-0">
<div class="wrap wrap--large">
<h1 class="mb-0">Confirm and pay</h1>
</div>
</div>
<div class="clear">
<div class="wrap wrap--small">
<?php perch_content('Intro text'); ?>
<?php
// Show the cart
perch_shop_cart([
'template'=>'cart/cart.html'
]);
?>
<hr>
<style>.loggedout {display: none;}</style>
<div class="grid mb-0 <?php echo $status ?>">
<div class="grid__6"><?php perch_shop_order_addresses(); ?></div>
<div class="grid__6">
<div class="tabs">
<h2>Pay here</h2>
<input id="tab-1" type="radio" name="type" value="1" checked="checked" aria-selected="true" aria-controls="card-tab" class="tabs__state">
<label for="tab-1"> Pay direct with card</label>
<input id="tab-2" type="radio" name="type" value="2" aria-controls="paypal-panel" class="tabs__state">
<label for="tab-2"> Pay with Paypal</label>
<div class="tab tab-1" role="tabpanel" aria-labelledby="card-tab">
<?php
// Show the payment form if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
perch_shop_payment_form('stripe_intents');
}
else echo "There is nothing in the cart";
?>
<br>
<p class="text-colour-grey">(Quick and easy Stripe payment - 100% secure)</p>
</div>
<div class="tab tab-2" role="tabpanel" aria-labelledby="paypal-tab">
<?php
// Show the paypal button if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
echo '<form action="" method="post"><button name="paypal" class="button button--primary" value="true">Pay with Paypal</button></form>
<br>
<p class="text-colour-grey">(This will take you off to the Paypal website)</p>
';
}
else echo "There is nothing in the cart";
?>
</div>
</div>
</div>
</div>
<?php
// Show the cancel link if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
echo '<p><a href="logout" style="color:red;">Cancel order</a></p>';
}
?>
</div>
</div>
<div class="clear">
<div class="wrap wrap--small">
<?php perch_content('Shop footer'); ?>
</div>
</div>
<script src="/js/perch_stripe_checkout_es5.js"></script>
<?php
perch_layout('footer', array(
'hide-social' => true,
));
?>
There were some issue with my examples, but I've tried it on site locally now and fixed the issues. All you need to change is the stripe_intents_payment_form.html. The below should work for you
<form id="stripeForm" action="/checkout" method="post">
<div id="card-element"></div>
<!-- Hidden fields used to send to stripe checkout -->
<input type="hidden" value="<perch:member id="email" escape="true" />" name="email"/>
<input type="hidden" value="<perch:member id="first_name" escape="true" /> <perch:member id="last_name" escape="true" />" name="cardholderName"/>
<!-- Address Fields if passed through -->
<input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
<!-- ./Address Fields if passed through -->
<input type="hidden" id="paymentMethodID" name="paymentMethodID"/>
<!-- ./Hidden fields used to send to stripe checkout -->
<!-- Regular button as we want to submit with JS -->
<button id="card-button" type="button" class="button button--primary">Make Payment</button>
</form>
<script src="https://js.stripe.com/v3/"></script>
<script>
function PerchStripeCheckout ( key = '' ) {
//Check if stripe has been loaded
if ( typeof Stripe != 'undefined' ) {
var stripe = Stripe( key );
var elements = stripe.elements();
}
// Get the card element container
if ( document.querySelector( '#card-element' ) ) {
// Create stripe card elements
var cardElement = elements.create( 'card', {
hidePostalCode: true
} );
cardElement.mount( '#card-element' );
// Get hidden fields for validation
var cardholderName = document.querySelector( '[name="cardholderName"]' );
var email = document.querySelector( '[name="email"]' );
var address = document.querySelector( '[name="address"]' );
var city = document.querySelector( '[name="city"]' );
var postcode = document.querySelector( '[name="postcode"]' );
var cardButton = document.getElementById( 'card-button' );
var paymentMethodID = document.querySelector( '[name="paymentMethodID"]' );
var form = document.querySelector( '#stripeForm' );
// Add listener for the form button
cardButton.addEventListener( 'click', function( ev ) {
// Get payment intent from stripe
stripe.createPaymentMethod( 'card', cardElement, {
billing_details: {
name: cardholderName.value,
email: email.value,
address: {
line1: address.value,
postal_code: postcode.value,
city: city.value,
country: 'GB'
}
}
} ).then( function( result ) {
paymentMethodID.value = result.paymentMethod.id;
// Submit form for next step
form.submit();
} );
} );
}
}
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PerchStripeCheckout('<perch:shop id="publishable_key" escape="true">');
});
</script>
The main script tag block can be moved into a seperate JS file and included separately. You'll need to update this somewhat if you want to add/remove fields to send to stripe or if you want to edit the stripe elements stylings. You can see all the options in their docs. For styling options see the docs here
Thanks Byron, we're getting somewhere now!
Quick question - are all these vendor files in the project src vital and need copying across/replacing my current files?
braintree/braintree_php clue/stream-filter composer guzzlehttp league/csv moneyphp/money omnipay php-http psr ralouphie/getallheaders stripe/stripe-php symfony autoload.php
(https://github.com/RootStudio/Root-Perch-Shop-Omnipay-v3/tree/master/perch_shop/lib/vendor)
Yeah they'll need to be copied over, as this includes the updated omnipay package for stripe to use the intents API
Thanks. Make Payment button is now having an effect - but the payment is not successful so it just redirects to the confirmation page and displays the unsuccessful message. Wondering how I can debug this?
Relevant files if helpful:
confirm.php
<?php
include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');
// hide the address/payment options with css
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
$status = 'loggedin';
// pass to stripe basics
$customer_firstname = perch_member_get('first_name');
$customer_surname = perch_member_get('last_name');
$customer_name = $customer_firstname . ' ' . $customer_surname;
$customer_email = perch_member_get('email');
// pass to stripe cart titles, skus
$cart = perch_shop_cart(['skip-template'=>true]);
$cart_items = $cart['items'];
foreach($cart_items as $item) {
$product_titles[] = $item['title'];
$product_skus[] = $item['sku'];
}
$product_title = implode(', ', $product_titles);
$product_sku = implode(', ', $product_skus);
}
else {
$status = 'loggedout';
}
// stripe urls
if (perch_member_logged_in() && perch_post('paymentMethodID')) {
$return_url = '/pay/result-stripe';
$cancel_url = '/pay/result-stripe';
// stripe data
perch_shop_checkout('stripe_intents', [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
//'token' => perch_post('stripeToken'),
'payment_method' => perch_post('paymentMethodID'), // Pass the payment method to the gateway
'receipt_email' => $customer_email,
'metadata' => [
'email' => $customer_email,
'name' => $customer_name,
'product(s)' => $product_title,
'sku(s)' => $product_sku,
],
'confirm' => true, // Tell the gateway to confirm the gateway
]);
}
// paypal urls
if (perch_member_logged_in() && isset($_POST["paypal"])) {
$return_url = 'https://www.eslstarter.com/pay/result-paypal';
$cancel_url = 'https://www.eslstarter.com/pay/error';
perch_shop_checkout('paypal-express', [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
]);
}
perch_layout('header', array(
'title' => "Confirm your order",
));
?>
<div class="clear feature-block ta-c mb-0">
<h1 class="mb-0">Confirm and pay</h1>
</div>
<div class="clear">
<div class="wrap wrap--small">
<?php perch_content('Intro text'); ?>
<?php
// Show the cart
perch_shop_cart([
'template'=>'cart/cart.html'
]);
?>
<hr>
<style>.loggedout {display: none;}</style>
<div class="grid mb-0 <?php echo $status ?>">
<div class="grid__6"><?php perch_shop_order_addresses(); ?></div>
<div class="grid__6">
<div class="tabs">
<h2>Pay here</h2>
<input id="tab-1" type="radio" name="type" value="1" checked="checked" aria-selected="true" aria-controls="card-tab" class="tabs__state">
<label for="tab-1"> Pay direct with card</label>
<input id="tab-2" type="radio" name="type" value="2" aria-controls="paypal-panel" class="tabs__state">
<label for="tab-2"> Pay with Paypal</label>
<div class="tab tab-1" role="tabpanel" aria-labelledby="card-tab">
<?php
// Show the payment form if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
perch_shop_payment_form('stripe_intents');
}
else echo "There is nothing in the cart";
?>
<br>
<p class="text-colour-grey">(Quick and easy Stripe payment - 100% secure)</p>
</div>
<div class="tab tab-2" role="tabpanel" aria-labelledby="paypal-tab">
<?php
// Show the paypal button if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
echo '<form action="" method="post"><button name="paypal" class="button button--primary" value="true">Pay with Paypal</button></form>
<br>
<p class="text-colour-grey">(This will take you off to the Paypal website)</p>
';
}
else echo "There is nothing in the cart";
?>
</div>
</div>
</div>
</div>
<?php
// Show the cancel link if logged in and something in cart
if (perch_member_logged_in() && perch_shop_cart_item_count([], true) > 0) {
echo '<p><a href="logout" style="color:red;">Cancel order</a></p>';
}
?>
</div>
</div>
<div class="clear">
<div class="wrap wrap--small">
<?php perch_content('Shop footer'); ?>
</div>
</div>
<?php
perch_layout('footer', array(
'hide-social' => true,
));
?>
result-stripe.php
<?php
include($_SERVER['DOCUMENT_ROOT'].'/admin/runtime.php');
/**
* Here we check if the payment was redirected or not. If the payment_intent field exists we need to finish the payment
*/
if (PerchUtil::get('payment_intent')) {
$payment_opts = [
'paymentIntentReference' => PerchUtil::get('payment_intent'), // set the reference ID
'returnUrl' => 'http://esl/pay/result-stripe',
'confirm' => true, // We are confirming the payment so we want this true
];
// New method to confirm the payment. Use the above options and the new intents gateway
perch_shop_confirm_payment('stripe_intents', $payment_opts);
}
perch_layout('header', array(
'title' => "Your order",
));
?>
<div class="clear feature-block ta-c mb-0">
<h1 class="mb-0">Your order</h1>
</div>
<div class="clear">
<div class="wrap wrap--small">
<?php
perch_shop_complete_payment('stripe_intents');
if (perch_shop_order_successful()) {
echo '<h2>Thank you for your order!</h2>
<p>We have successfully received your payment from Stripe. You should receive a confirmation email shortly. Please take a moment to like us on Facebook and sign up for our newsletter (see footer below).</p>
<p><a href="/">Back to homepage</a></p>';
}
else{
echo '<h2>Important: It looks like the transaction was unsuccessful</h2>
<p><strong>Please check your email</strong>. If you have received a confirmation email then your order was successful and there is nothing more you need to do.</p>
<p>If no emails have arrived after 10 minutes please email us and one of our staff will be in touch as soon as possible. Include in the email your name, what you were trying to order and what happened. You can also call us on <strong>0800 368 2255</strong> (Mon-Fri 9am-5pm).</p>
<p>If you accidentally cancelled the order you can go back to <a href="/pay/confirm">the cart</a> and try again.</p>';
}
?>
</div>
</div>
<?php
perch_layout('footer', array(
'hide-social' => true,
));
?>
Your current stripe URLs are using relative paths, you'll need to use am absolute path for these to work correctly
Payments now going through, thanks Byron.
Quick one - how do I pass these through to stripe_intents_payment_form.html? Name and email are both present so wondering why these ones aren't. Thank you
<!-- Address Fields if passed through -->
<input type="hidden" value="<perch:member id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:member id="postcode" escape="true" />" name="postcode"/>
<!-- ./Address Fields if passed through -->
You'll need to set them as global perch variables using the PerchSystem::set_var(). You can use the perch address functions to get the address details.
It seemed to work fine as is without this on our first shop, but we must have had some other configurations that set the address else where
I thought that PerchSystem::set_var() had to be used in conjunction with perch_content_custom()? I think I need to get the values passed into perch_shop_payment_form('stripe_intents')
PerchSystem::set_var() just sets a global variable. Whenever perch calls it's template renderer all these variables are passed through, not just in the content custom runtime function.
Okay that's good to know, thanks!
I'm stuck now at the final hurdle. I can't figure out how to get, for example, the city out of perch_shop_customer_address
stripe_intents_payment_form.html
<input type="hidden" value="<perch:member id="city" escape="true" />" name="city"/>
checkout.php
PerchSystem::set_var('city', perch_shop_customer_address('??'));
I've tried a few things but no dice.
It looks like it could be a namespace issue. the perch:member namespace lets you retrieve member information outside of the current namespace, but only if it relates to the members app. Otherwise you'll need to be using the perch:shop namespace
Hi Byron,
I seem to have got it working using the following:
stripe_intents_payment_form.html
<input type="hidden" value="<perch:shop id="addressLine1" escape="true" />" name="address"/>
<input type="hidden" value="<perch:shop id="city" escape="true" />" name="city"/>
<input type="hidden" value="<perch:shop id="postcode" escape="true" />" name="postcode"/>
checkout.php
$addresses = perch_shop_customer_addresses(['skip-template'=>true]);
PerchSystem::set_var('addressLine1', $addresses[0]["address_1"]);
PerchSystem::set_var('city', $addresses[0]["city"]);
PerchSystem::set_var('postcode', $addresses[0]["postcode"]);
Does that look right to you?
That looks fine to me.
If you wanted to clean it up a bit you could do PerchSystem::set_vars($addresses[0]);
Thanks for all your help.
Hi Byron, thanks for your work on this.
I've done my best to implement the new files and code to my project, but when clicking on Make Payment - alas - nothing happens.
In the console I've got the following errors:
*The publishable_key is showing in showall, although I can tell this is coming from the 'stripe' gateway in shop.php and not 'stripe_intents' (I have both copied in there having read one of the other issues someone had).
Which files would you like me to paste in here to help find the problem?
Thanks again.