Skip to content

Commit

Permalink
Refactor payment page with Vue.js
Browse files Browse the repository at this point in the history
Co-Authored-By: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
driesvints and taylorotwell committed Jul 11, 2019
1 parent 64ea5f7 commit 9396cee
Showing 1 changed file with 92 additions and 47 deletions.
139 changes: 92 additions & 47 deletions resources/views/payment.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,31 @@
<title>{{ __('Payment Confirmation') }} - {{ config('app.name', 'Laravel') }}</title>

<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>

<script src="https://js.stripe.com/v3"></script>
</head>
<body class="font-sans text-gray-600 bg-gray-200 leading-normal p-4 h-full">
<div class="h-full md:flex md:justify-center md:items-center">
<div id="app" class="h-full md:flex md:justify-center md:items-center">
<div class="w-full max-w-lg">
<p id="message" class="hidden mb-4 bg-red-100 border border-red-400 px-6 py-4 rounded-lg text-red-600"></p>
<!-- Status Messages -->
<p class="flex items-center mb-4 bg-red-100 border border-red-200 px-5 py-2 rounded-lg text-red-500" v-if="errorMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6">
<path class="fill-current text-red-300" d="M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20z"/>
<path class="fill-current text-red-500" d="M12 18a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm1-5.9c-.13 1.2-1.88 1.2-2 0l-.5-5a1 1 0 0 1 1-1.1h1a1 1 0 0 1 1 1.1l-.5 5z"/>
</svg>

<span class="ml-3">@{{ errorMessage }}</span>
</p>

<p class="flex items-center mb-4 bg-green-100 border border-green-200 px-5 py-4 rounded-lg text-green-700" v-if="paymentProcessed && successMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6">
<circle cx="12" cy="12" r="10" class="fill-current text-green-300"/>
<path class="fill-current text-green-500" d="M10 14.59l6.3-6.3a1 1 0 0 1 1.4 1.42l-7 7a1 1 0 0 1-1.4 0l-3-3a1 1 0 0 1 1.4-1.42l2.3 2.3z"/>
</svg>

<span class="ml-3">@{{ successMessage }}</span>
</p>

<div class="bg-white rounded-lg shadow-xl p-4 sm:py-6 sm:px-10 mb-5">
@if ($payment->isSucceeded())
Expand All @@ -29,7 +47,8 @@

<p class="mb-6">{{ __('This payment was cancelled.') }}</p>
@else
<div id="payment-elements">
<div id="payment-elements" v-if="! paymentProcessed">
<!-- Instructions -->
<h1 class="text-xl mt-2 mb-4 text-gray-700">
{{ __('Confirm your :amount payment', ['amount' => $payment->amount()]) }}
</h1>
Expand All @@ -38,14 +57,24 @@
{{ __('Extra confirmation is needed to process your payment. Please confirm your payment by filling out your payment details below.') }}
</p>

<!-- Name -->
<label for="cardholder-name" class="inline-block text-sm text-gray-700 font-semibold mb-2">{{ __('Full name') }}</label>

<input id="cardholder-name" type="text" placeholder="Jane Doe" required
class="inline-block bg-gray-200 border border-gray-400 rounded-lg w-full px-4 py-3 mb-3">
class="inline-block bg-gray-200 border border-gray-400 rounded-lg w-full px-4 py-3 mb-3 focus:outline-none"
v-model="name">

<!-- Card -->
<label for="cardholder-name" class="inline-block text-sm text-gray-700 font-semibold mb-2">{{ __('Card') }}</label>

<div id="card-element" class="bg-gray-200 border border-gray-400 rounded-lg p-4 mb-6"></div>

<button id="card-button" class="inline-block w-full px-4 py-3 mb-4 bg-blue-600 text-white rounded-lg hover:bg-blue-500">
<!-- Pay Button -->
<button id="card-button"
class="inline-block w-full px-4 py-3 mb-4 text-white rounded-lg hover:bg-blue-500"
:class="{ 'bg-blue-400': paymentProcessing, 'bg-blue-600': ! paymentProcessing }"
@click="confirmPayment"
:disabled="paymentProcessing">
{{ __('Pay :amount', ['amount' => $payment->amount()]) }}
</button>
</div>
Expand All @@ -55,54 +84,70 @@ class="inline-block bg-gray-200 border border-gray-400 rounded-lg w-full px-4 py
class="inline-block w-full px-4 py-3 bg-gray-200 hover:bg-gray-300 text-center text-gray-700 rounded-lg">
{{ __('Go back') }}
</a>

<script>
const paymentElements = document.getElementById('payment-elements');
const cardholderName = document.getElementById('cardholder-name');
const cardButton = document.getElementById('card-button');
const message = document.getElementById('message');
const stripe = Stripe('{{ $stripeKey }}');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
cardButton.addEventListener('click', function() {
stripe.handleCardPayment(
'{{ $payment->clientSecret() }}', cardElement, {
payment_method_data: {
billing_details: { name: cardholderName.value }
}
}
).then(function (result) {
if (result.error) {
if (result.error.code === 'parameter_invalid_empty' &&
result.error.param === 'payment_method_data[billing_details][name]') {
message.innerText = '⚠️ {{ __('Please provide your name.') }}';
} else {
message.innerText = '⚠️ '+result.error.message;
}
message.classList.add('text-red-600', 'border-red-400', 'bg-red-100');
message.classList.remove('text-green-600', 'border-green-400', 'bg-green-100');
} else {
paymentElements.classList.add('hidden');
message.innerText = '{{ __('The payment was successful.') }}';
message.classList.remove('text-red-600', 'border-red-400', 'bg-red-100');
message.classList.add('text-green-600', 'border-green-400', 'bg-green-100');
}
message.classList.remove('hidden');
});
});
</script>
</div>

<p class="text-center text-gray-500 text-sm">
© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }}
</p>
</div>
</div>

<script>
window.stripe = Stripe('{{ $stripeKey }}');
var app = new Vue({
el: '#app',
data: {
name: '',
cardElement: null,
paymentProcessing: false,
paymentProcessed: false,
successMessage: '',
errorMessage: ''
},
mounted: function () {
const elements = stripe.elements();
this.cardElement = elements.create('card');
this.cardElement.mount('#card-element');

This comment has been minimized.

Copy link
@u01jmg3

u01jmg3 Aug 28, 2019

Contributor

Should this be wrapped in an @if (! $payment->isSucceeded() && ! $payment->isCancelled()) condition in case the user refreshes the page and has already successfully paid which means #card-element doesn't exist to mount to?

This comment has been minimized.

Copy link
@driesvints

driesvints Aug 29, 2019

Author Member

Hmm, yeah. I'll try to fix this soon or feel free to send in a PR.

This comment has been minimized.

Copy link
@driesvints

driesvints Aug 29, 2019

Author Member

Thanks for reporting btw!

This comment has been minimized.

Copy link
@u01jmg3

u01jmg3 Aug 29, 2019

Contributor

No problem - PR raised

},
methods: {
confirmPayment: function () {
var self = this;
this.paymentProcessing = true;
this.paymentProcessed = false;
this.successMessage = '';
this.errorMessage = '';
stripe.handleCardPayment(
'{{ $payment->clientSecret() }}', this.cardElement, {
payment_method_data: {
billing_details: { name: this.name }
}
}
).then(function (result) {
self.paymentProcessing = false;
if (result.error) {
if (result.error.code === 'parameter_invalid_empty' &&
result.error.param === 'payment_method_data[billing_details][name]') {
self.errorMessage = '{{ __('Please provide your name.') }}';
} else {
self.errorMessage = result.error.message;
}
} else {
self.paymentProcessed = true;
self.successMessage = '{{ __('The payment was successful.') }}';
}
});
},
},
})
</script>
</body>
</html>

0 comments on commit 9396cee

Please sign in to comment.