Challenges with integrating Stripe

  • I haven’t been able to find any real solid tutorials / examples of integrating payment into a Meteor app, just what’s available using Nodes.JS with Express

  • Stripe guide I’m trying (and failing) to follow / integrate into my app

  • This is a possible solution, but I could use help dissecting it for Stripe integration following the guide listed above

  • Are there any simple guides that do not include mrgalaxy:stripe as their solution

  • That package hasn’t been updated since 2016 and I’d really prefer more up to date dependencies

  • I’m specifically stuck on await / async / promises and could use some help with the checkout portion

  • If I need to paste code I will, but mainly I’m looking for something more recent that can help me integrate a simple checkout process

Thanks!

What is failing for you? We used Stripe via the npm packages and didn’t have to veer off the instructions provided by Stripe. What part isn’t working?

Here’s the code for the client, then the client error I’m getting, and the section of code the error is referring to. Please note, I have not properly configured the backend portion yet, just trying to get this part somewhat functional and help understanding the guide I’m trying to follow over at Stripe.

Template.shopping_cart_template.onRendered(async function shopping_cart_template_onRendered() {
	const template = Template.instance();

	template.autorun(() => {
		template.data.dictionary.set('local_cart', JSON.parse(get_local_data('local_cart')) || []);
		template.data.dictionary.set('local_cart_item_count', get_local_data('local_cart_item_count') || 0);
		template.data.dictionary.set('local_cart_total', local_cart_total(template.data.dictionary.get('local_cart'))
			.total_without_taxes || []);

		template.data.dictionary.get('local_cart');
		template.data.dictionary.get('local_cart_item_count');
		template.data.dictionary.get('local_cart_total');

		template.not_ready_to_checkout.get();
		template.item_quantity.get();

		Session.set('account_navigation_active', 'Profile');

		if (template.data.dictionary.get('local_cart')
			.length) {
			template.data.dictionary.set('local_cart_ready', true);
		}

		if (Session.get('local_cart_reload')) {
			template.data.dictionary.set('local_cart', JSON.parse(get_local_data('local_cart')) || []);
			template.not_ready_to_checkout.set(true);
			template.checkout_process.set('step', 'cart');
			Session.set('local_cart_reload', false);
		}
	});

	const stripe = await loadStripe(Meteor.settings.public.stripe.testPublishableKey);
	const elements = stripe.elements();
	const cardElement = elements.create('card');
	console.log('### stripe: ', stripe);
	console.log('### elements: ', elements);
	console.log('### cardElement: ', cardElement);
	cardElement.mount('#card-element');
	console.log('### cardElement.mount: ', cardElement);

	const form = document.getElementById("payment-form");
	console.log('### form: ', form);
	const resultContainer = document.getElementById('payment-result');
	cardElement.on('change', function (event) {
		if (event.error) {
			resultContainer.textContent = event.error.message;
			console.log('### resultContainer.textContent ERROR: ', resultContainer.textContent);
		} else {
			resultContainer.textContent = '';
			console.log('### resultContainer.textContent: ', resultContainer.textContent);
		}
	});

	form.addEventListener('submit', async event => {
		event.preventDefault();
		console.log('### event: ', event);
		resultContainer.textContent = '';
		console.log('### cardElement: ', cardElement);
		const result = await stripe.createPaymentMethod({
			type: 'card',
			card: cardElement,
		});
		console.log('### result: ', result);
		handlePaymentMethodResult(result);
	});

	const handlePaymentMethodResult = async ({
		paymentMethod,
		error
	}) => {
		if (error) {
			// An error happened when collecting card details, show error in payment form
			resultContainer.textContent = result.error.message;
		} else {
			// Send paymentMethod.id to your server (see Step 3)
			const response = await fetch("/pay", {
				method: "POST",
				headers: {
					"Content-Type": "application/json"
				},
				body: JSON.stringify({
					payment_method_id: paymentMethod.id
				})
			});

			const responseJson = await response.json();

			handleServerResponse(responseJson);
		}
	};

	const handleServerResponse = async responseJson => {
		if (responseJson.error) {
			// An error happened when charging the card, show it in the payment form
			resultContainer.textContent = responseJson.error;
		} else {
			// Show a success message
			resultContainer.textContent = 'Success!';
		}
	};
});

Client error I’m receiving:

shopping-cart.js:139 Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
async function (async)
handlePaymentMethodResult @ shopping-cart.js:125
(anonymous) @ shopping-cart.js:113
async function (async)
(anonymous) @ shopping-cart.js:108

Portion the error is pointing to that’s problematic:

form.addEventListener('submit', async event => {
	event.preventDefault();
	console.log('### event: ', event);
	resultContainer.textContent = '';
	console.log('### cardElement: ', cardElement);
	const result = await stripe.createPaymentMethod({
		type: 'card',
		card: cardElement,
	});
	console.log('### result: ', result);
	handlePaymentMethodResult(result);
});

const handlePaymentMethodResult = async ({
	paymentMethod,
	error
}) => {
	if (error) {
		// An error happened when collecting card details, show error in payment form
		resultContainer.textContent = result.error.message;
	} else {
		// Send paymentMethod.id to your server (see Step 3)
		const response = await fetch("/pay", {
			method: "POST",
			headers: {
				"Content-Type": "application/json"
			},
			body: JSON.stringify({
				payment_method_id: paymentMethod.id
			})
		});

		const responseJson = await response.json();

		handleServerResponse(responseJson);
	}
};

Let me know what else you need and I’ll provide it as soon as I can. Thank you very much for your assistance!

This error almost always indicates that the thing returned from your backend is HTML (or is showing an error in HTML) instead of JSON as expected:

Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0

I’m not super familiar with Meteor, but I am with Stripe+node—what code do you have handling the request to fetch('/pay', …), and what output do you see in response to the frontend request when you check in your browser’s Network Inspector?

I have just got stripe working with Meteor and React so not sure how that will help you with your Blaze implementation. In the React case, there is a github repo from stripe that lays it all out. That said, it wasn’t straight forward for me as I wanted the back end involved in the Payment Intent process and the stripe docs are prodigious and for me, it was somewhat difficult to be sure I was doing things in the right order. There are a great many ways to create a stripe implementation and older ones are not encouraged, though still supported.

This is your issue, you are posting the request to a backend that doesn’t exist.
Because Meteor is primarily a SPA, it always serves the app HTML and bundle for unknown urls, so the client side router can deal with it.

That means when you POST to /pay, you are getting the page HTML back, which throws when parsed as JSON here:

    const responseJson = await response.json();

You can create a HTTP backend using WebApp.connectHandlers, or use a Meteor Method to pass the data to the backend and use Stripe on the backend there.
Meteor Methods are preferred as they include the user session and can more easily update the customer record

4 Likes

I’m going to give this a shot tonight and see how it resolves itself (hopefully). WebApp difficult to integrate? Haven’t had to use it before.

Thanks btw!

I would strongly recommend you try using Methods first, otherwise you’re going to have issues with identifying payments to users

Okay, I’ll go the route of Methods then. Thanks again for the advice!