Checkboxes and function changes were also made for for optionally saving cards. The new payment-card component needed to be created to mount the stripe card correctly after DOM changes.tags/v0.1.0
@@ -7,6 +7,7 @@ use Stripe\Stripe; | |||||
use Stripe\Customer; | use Stripe\Customer; | ||||
use Stripe\PaymentIntent; | use Stripe\PaymentIntent; | ||||
use Illuminate\Support\Facades\Log; | use Illuminate\Support\Facades\Log; | ||||
use Illuminate\Support\Facades\Auth; | |||||
class BillingController extends Controller | class BillingController extends Controller | ||||
{ | { | ||||
@@ -27,7 +28,7 @@ class BillingController extends Controller | |||||
$intent = PaymentIntent::create([ | $intent = PaymentIntent::create([ | ||||
'amount' => $amount, | 'amount' => $amount, | ||||
'currency' => 'usd', | 'currency' => 'usd', | ||||
/* 'customer' => Auth::user().customer_id, */ | |||||
'customer' => Auth::user()->customer_id, | |||||
'metadata' => ['integration_check' => 'accept_a_payment'] | 'metadata' => ['integration_check' => 'accept_a_payment'] | ||||
]); | ]); | ||||
@@ -24,7 +24,8 @@ class UserController extends Controller | |||||
'email' => 'required|email|unique:users,email', | 'email' => 'required|email|unique:users,email', | ||||
'password' => 'required|confirmed|min:8|regex:/[a-z]/|regex:/[A-Z]/|regex:/[0-9]/' | 'password' => 'required|confirmed|min:8|regex:/[a-z]/|regex:/[A-Z]/|regex:/[0-9]/' | ||||
]); | ]); | ||||
/* Stripe::setApiKey(); */ | |||||
Stripe::setApiKey(env('STRIPE_SECRET')); | |||||
$user = new User; | $user = new User; | ||||
$user->name = $request->name; | $user->name = $request->name; | ||||
@@ -19,6 +19,7 @@ class DatabaseSeeder extends Seeder | |||||
*/ | */ | ||||
public function run() | public function run() | ||||
{ | { | ||||
Stripe::setApiKey(env('STRIPE_SECRET')); | |||||
Service::create([ | Service::create([ | ||||
'name' => 'Unique Views with Engagements', | 'name' => 'Unique Views with Engagements', | ||||
'type' => 'views', | 'type' => 'views', | ||||
@@ -19,29 +19,31 @@ | |||||
<div id="credits-errors"></div> | <div id="credits-errors"></div> | ||||
</section> | </section> | ||||
<section id="payment-section"> | <section id="payment-section"> | ||||
<h4>Payment Method</h4> | <h4>Payment Method</h4> | ||||
<form id="payment-form" action=""> | |||||
<label for="name">Name on Card</label> | |||||
<input id="billing-name" type="name"> | |||||
<div id="card-element"></div> | |||||
<div id="card-errors"></div> | |||||
<div id=save-card> | |||||
<input name="save-card" value="Save card" type="checkbox"> | |||||
<label for="">Save Card</label> | |||||
<div class="sliding-menu"> | |||||
<a @click="selectSaved = true" :class="{selected: selectSaved}">Saved Card</a> | |||||
<a @click="selectSaved = false" :class="{selected: !selectSaved}">New Card</a> | |||||
<div :class="{right: !selectSaved}" class="menu-slider"><div></div></div> | |||||
</div> | </div> | ||||
</form> | |||||
<payment-card :stripe="stripe" v-if="!selectSaved"></payment-card> | |||||
<div id="payment-error"></div> | <div id="payment-error"></div> | ||||
</section> | </section> | ||||
<section class="credits-confirm"> | <section class="credits-confirm"> | ||||
<button @click="pay" :disabled="total == 0 || loading" class="brand-btn">Pay</button> | |||||
<button @click="pay" :disabled="total == 0 || loading || !cardValid || !billingName" | |||||
class="brand-btn">Buy<loading v-if="loading"></loading></button> | |||||
</section> | </section> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import Loading from '../icons/loading.vue' | |||||
import PaymentCard from './payment-card.vue' | |||||
function total() { | function total() { | ||||
return this.packs.credits10*10.99 + this.packs.credits50*54.99 | return this.packs.credits10*10.99 + this.packs.credits50*54.99 | ||||
+ this.packs.credits100*109.99 + this.packs.credits1000*1010 | + this.packs.credits100*109.99 + this.packs.credits1000*1010 | ||||
@@ -49,6 +51,7 @@ function total() { | |||||
//Gets the secret key specific to chosen payment amount and user | //Gets the secret key specific to chosen payment amount and user | ||||
function getSecret() { | function getSecret() { | ||||
document.getElementById('credits-errors').textContent = '' | |||||
this.loading = true | this.loading = true | ||||
return fetch('/panel/secret', { | return fetch('/panel/secret', { | ||||
method: 'POST', | method: 'POST', | ||||
@@ -69,29 +72,17 @@ function getSecret() { | |||||
}) | }) | ||||
} | } | ||||
function mountPaymentForm() { | |||||
this.card = this.stripe.elements().create('card') | |||||
this.card.mount('#card-element') | |||||
this.card.on('change', function(event) { | |||||
let displayError = document.getElementById('card-errors'); | |||||
if (event.error) { | |||||
displayError.textContent = event.error.message; | |||||
} else { | |||||
displayError.textContent = ''; | |||||
} | |||||
}); | |||||
} | |||||
//Gets key from the server then sends it with stripe | //Gets key from the server then sends it with stripe | ||||
function pay() { | function pay() { | ||||
this.getSecret().then(secret => { | this.getSecret().then(secret => { | ||||
if (!secret) {return} | if (!secret) {return} | ||||
this.loading = true | |||||
return this.stripe.confirmCardPayment(secret, { | return this.stripe.confirmCardPayment(secret, { | ||||
payment_method: { | payment_method: { | ||||
card: this.card, | card: this.card, | ||||
billing_details: {name: | billing_details: {name: | ||||
document.getElementById('billing-name').value} | |||||
document.getElementById('billing-name').value}, | |||||
setup_future_usage: document.querySelector('#save-card input') == 'on' ? 'off_session' : null | |||||
} | } | ||||
}) | }) | ||||
}).then(result => { | }).then(result => { | ||||
@@ -105,21 +96,23 @@ function pay() { | |||||
document.getElementById('payment-error').textContent = | document.getElementById('payment-error').textContent = | ||||
'An unknown error occured' | 'An unknown error occured' | ||||
} | } | ||||
this.loading = false | |||||
}) | }) | ||||
} | } | ||||
export default { | export default { | ||||
components:{Loading, PaymentCard}, | |||||
data() { | data() { | ||||
return {packs: {credits10: 0, credits50: 0, | return {packs: {credits10: 0, credits50: 0, | ||||
credits100: 0, credits1000: 0}, loading: false, stripe: | credits100: 0, credits1000: 0}, loading: false, stripe: | ||||
Stripe(process.env.VUE_APP_STRIPE_KEY), card: | Stripe(process.env.VUE_APP_STRIPE_KEY), card: | ||||
null} | |||||
null, cardValid: false, billingName: null, selectSaved: true} | |||||
}, | }, | ||||
computed: {total}, | computed: {total}, | ||||
methods: {getSecret, mountPaymentForm, pay}, | |||||
methods: {getSecret, pay}, | |||||
props: ['token'], | props: ['token'], | ||||
mounted() { | mounted() { | ||||
this.mountPaymentForm() | |||||
/* this.mountPaymentForm() */ | |||||
} | } | ||||
} | } | ||||
</script> | </script> |
@@ -0,0 +1,38 @@ | |||||
<template> | |||||
<form id="payment-form" action=""> | |||||
<label for="name">Name on Card</label> | |||||
<input v-model="billingName" id="billing-name" type="name"> | |||||
<div id="card-element"></div> | |||||
<div id="card-errors"></div> | |||||
<div id=save-card> | |||||
<input name="save-card" type="checkbox" checked="true"> | |||||
<label for="">Save Card</label> | |||||
</div> | |||||
</form> | |||||
</template> | |||||
<script> | |||||
function mountPaymentForm() { | |||||
this.card = this.stripe.elements().create('card') | |||||
this.card.mount('#card-element') | |||||
this.card.on('change', function(event) { | |||||
let displayError = document.getElementById('card-errors'); | |||||
if (event.error) { | |||||
displayError.textContent = event.error.message; | |||||
} else { | |||||
displayError.textContent = ''; | |||||
this.cardValid = true | |||||
} | |||||
}.bind(this)); | |||||
} | |||||
export default { | |||||
data() { | |||||
return {billingName: null} | |||||
}, | |||||
methods: {mountPaymentForm}, | |||||
props: ['stripe'], | |||||
mounted: mountPaymentForm | |||||
} | |||||
</script> |
@@ -0,0 +1,17 @@ | |||||
<template> | |||||
<div class="services-menu"> | |||||
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a> | |||||
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a> | |||||
<div :class="page" class="menu-slider"><div></div></div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
items: null | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -1,6 +1,6 @@ | |||||
<template> | <template> | ||||
<div> | <div> | ||||
<div class="services-menu"> | |||||
<div class="sliding-menu"> | |||||
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a> | <a href="#new-order" :class="{selected: page == 'new-order'}">Services</a> | ||||
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a> | <a href="#credits" :class="{selected: page == 'credits'}">Credits</a> | ||||
<div :class="page" class="menu-slider"><div></div></div> | <div :class="page" class="menu-slider"><div></div></div> | ||||
@@ -995,7 +995,7 @@ main.panel { | |||||
} | } | ||||
} | } | ||||
.services-menu { | |||||
.sliding-menu { | |||||
display: flex; | display: flex; | ||||
justify-content: center; | justify-content: center; | ||||
flex-basis: 50%; | flex-basis: 50%; | ||||
@@ -1026,7 +1026,10 @@ main.panel { | |||||
} | } | ||||
.menu-slider.credits div { | .menu-slider.credits div { | ||||
// margin-left: auto; | |||||
transform: translateX(100%); | |||||
} | |||||
.menu-slider.right div { | |||||
transform: translateX(100%); | transform: translateX(100%); | ||||
} | } | ||||
@@ -1233,3 +1236,9 @@ div#card-errors { | |||||
} | } | ||||
} | } | ||||
.loading-overlay { | |||||
position: absolute; | |||||
width: 100%; | |||||
height: 100%; | |||||
} |