Skip to content

Instantly share code, notes, and snippets.

@edwardlorilla
Created April 30, 2021 15:06
Show Gist options
  • Save edwardlorilla/309b70425b299745cba9a0bc9cf42250 to your computer and use it in GitHub Desktop.
Save edwardlorilla/309b70425b299745cba9a0bc9cf42250 to your computer and use it in GitHub Desktop.
【VUE JS】Checking out with payment request API
<div id="app" class="container">
<h2>Cookies - fresh from our oven!</h2>
<h3>All just 75¢ each</h3>
<div class="text-right"><span class="cartcount">{{cartItems.length}} item(s) in your basket</span></div>
<div class="container">
<div class="row">
<div class="col-xs-3 text-center" v-for="item in items">
<div class="img"><img class="img-responsive" :src="item.image" alt=""></div>
<h5>{{ item.title }}</h5>
<p class="text-center">
<input v-model="item.qty" type="number" class="form-control" placeholder="Qty" min="1"/></p>
<button @click="addToCart(item)" class="btn btn-sm btn-primary">Add</button>
</p>
</div>
</div>
</div>
<h4>Cart</h4>
<shopping-cart inline-template :items="cartItems">
<div>
<div v-for="(item, index) in items" class="product">
<div class="producttitle">{{item.title}}</div>
<div style="width:150px">Quantity: <input v-model="item.qty" class="form-control input-qty" type="number"></div>
<div class="price">${{item.price | formatCurrency}}</div>
<div class="discard" @click="removeItem(index)"><span class="glyphicon glyphicon-trash"></span></div>
</div>
<table class="table table-cart">
<tr v-show="items.length === 0">
<td colspan="4" class="text-center">Cart is empty</td>
</tr>
<tr v-show="items.length > 0">
<td class="blank"></td>
<td class="carttotal">Cart Total</td>
<td class="cartamt">{{Total | formatCurrency}}</td>
</tr>
</table>
<div class="checkout-div" v-show="items.length > 0">
<button type="button" class="btn btn-default checkout" v-on:click="checkout">Checkout securely</button>
</div>
<div id="message"></div>
<div id="instructions">
<h2>Add delivery instructions</h2>
<p>Have any special requirements? You still have time to let us know:</p>
<textarea id="additional-details-container"></textarea>
<button id="delinstruct">Submit</button>
</div>
</div>
</shopping-cart>
</div>
const methodData = [{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex']
}
}, {
supportedMethods: 'https://bobpay.xyz/pay'
}, {
supportedMethods: 'interledger'
}];
function displayMessage(symbol, status, mesg) {
document.getElementById("message").classList.remove();
document.getElementById("message").classList.add(status);
document.getElementById("message").innerHTML = "<span>" + symbol + "</span>" + mesg;
}
function updateDetails(details, shippingOption, resolve, stotal) {
if (shippingOption === 'standard') {
selectedOption = details.shippingOptions[0];
otherOption = details.shippingOptions[1];
details.total.amount.value = stotal;
} else {
selectedOption = details.shippingOptions[1];
otherOption = details.shippingOptions[0];
details.total.amount.value = (Number(stotal) + Number(3.99)).toFixed(2);
}
selectedOption.selected = true;
otherOption.selected = false;
details.displayItems.splice(1, 1, selectedOption);
resolve(details);
}
function initCheckout (e) {
if(window.PaymentRequest) {
var subtotal = Number(document.querySelector(".cartamt").innerText);
var delivery = 0.00;
var beforetax = (subtotal + delivery)
var tax = Number( beforetax * 0.0575);
var total = Number(subtotal + tax + delivery).toFixed(2);
const paymentDetails = {
total: {
label: 'Total due',
amount: { currency: 'USD', value: total }
},
displayItems: [{
label: 'Sub-total',
amount: { currency: 'USD', value: subtotal }
}, {
label: 'FREE Delivery (3-5 days)',
amount: { currency: 'USD', value: delivery.toFixed(2) }
}, {
label: 'Sales Tax @ 5.75%',
amount: { currency: 'USD', value: tax.toFixed(2) }
}],
modifiers: [{
supportedMethods: 'https://bobpay.xyz/pay',
additionalDisplayItems: [{
label: 'Processing fee',
amount: { currency: 'USD', value: '3.00' }
}],
total: {
label: 'Total to pay by card',
amount: {currency: 'USD', value: Number(total + 3).toFixed(2)}}
}], shippingOptions: [{
id: 'standard',
label: 'FREE delivery (3-5 days)',
amount: {currency: 'USD', value: '0.00'},
selected: true,
},
{
id: 'express',
label: 'Express delivery (next day)',
amount: {currency: 'USD', value: '3.99'},
},
],
};
const options = { requestPayerEmail: true, requestShipping: true };
const request = new PaymentRequest(methodData, paymentDetails, options);
request.addEventListener('shippingaddresschange', function(evt) {
evt.updateWith(new Promise(function(resolve) {
updateDetails(paymentDetails, request.shippingAddress, resolve, total);
}));
});
request.addEventListener('shippingoptionchange', function(evt) {
evt.updateWith(new Promise(function(resolve) {
updateDetails(paymentDetails, request.shippingOption, resolve, total);
}));
});
if (request.canMakePayment) {
request.canMakePayment().then(function(result) {
if (result) {
request.show().then(function(result) {
result.complete('success').then(function() {
displayMessage("\u2714", "success", "Payment received - thanks for your order!");
const additionalDetailsContainer = document.getElementById('instructions');
additionalDetailsContainer.style.display = 'block';
additionalDetailsContainer.focus();
});
}).catch(function(err) {
if (err.code == DOMException.ABORT_ERR) {
console.error(err.message);
displayMessage("&#128712;", "info", "Request has been cancelled.");
} else {
console.error(err.message);
displayMessage("\u2716", "failure", "There was a problem with payment");
}
});
} else {
console.log('Cannot make payment');
displayMessage("&#128712;", "info", "Sorry - no valid payment methods available");
}
}).catch(function(err) {
console.log(request, err);
});
}
}
}
const products = [
{id: 1,title: 'Cherry Bakewell', price: 0.75, qty: 1, image: 'https://via.placeholder.com/100x100.png?text=cbakewell.png'},
{id: 2,title: 'Coconut',price: 0.75, qty: 1,image: 'https://via.placeholder.com/100x100.png?text=coconut.png'},
{id: 3,title: 'Dark Choc',price: 0.75,qty: 1,image: 'https://via.placeholder.com/100x100.png?text=dark-choc.png'},
{id: 4,title: 'Double Choc',price: 0.75, qty: 1, image: 'https://via.placeholder.com/100x100.png?text=double-choc.png'},
{id: 5,title: 'Jaffa', price: 0.75, qty: 1, image: 'https://via.placeholder.com/100x100.png?text=jaffa.png'},
{id: 6,title: 'Oatmeal & Raisin',price: 0.75, qty: 1,image: 'https://via.placeholder.com/100x100.png?text=oatmeal-rasin.png'},
{id: 7,title: 'Raspberry & White Choc',price: 0.75,qty: 1,image: 'https://via.placeholder.com/100x100.png?text=rasberry-white-choc.png'},
{id: 8,title: 'Toffee',price: 0.75, qty: 1, image: 'https://via.placeholder.com/100x100.png?text=toffee.png'}
];
function formatNumber(n, c, d, t){
var c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d === undefined ? '.' : d,
t = t === undefined ? ',' : t,
s = n < 0 ? '-' : '',
i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))),
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
}
Vue.filter('formatCurrency', function (value) {
return formatNumber(value, 2, '.', ',');
});
Vue.component('shopping-cart', {
props: ['items'],
computed: {
Total: function() {
var total = 0;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
},
methods: {
removeItem(index) {
this.items.splice(index, 1);
}
}
});
const vm = new Vue({
el: '#app',
data: {
cartItems: [],
items : products
},
methods: {
checkout: function(event) {
console.log("Checkout");
initCheckout();
},
addToCart(itemToAdd) {
var found = false;
// Check if the item was already added to cart
// If so them add it to the qty field
this.cartItems.forEach(item => {
if (item.id === itemToAdd.id) {
found = true;
item.qty += itemToAdd.qty;
}
});
if (found === false) {
this.cartItems.push(Vue.util.extend({}, itemToAdd));
}
itemToAdd.qty = 1;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js"></script>
body {
font-family: 'Amatic SC', cursive;
}
h2 {
font-size: 30px;
border-bottom: 1px solid #000;
padding-bottom: 5px;
font-family: 'Amatic SC', sans-serif;
font-size: 45px;
font-weight: bold;
}
h3 {
font-family: montserrat, sans-serif;
}
h4 {
border-bottom: 1px solid #000000;
font-size: 18px;
font-family: 'Montserrat', sans-serif;
margin-top: -10px;
}
h5 {
height: 30px;
font-size: 27px;
font-weight: 700;
}
.container{
padding:20px;
max-width: 850px;
}
.table > tbody > tr > td {
border-top: none;
}
.col-xs-3 {
height: 280px;
width: 205px;
}
.img {
width: 150px;
height: 150px;
display: block;
margin-left: auto;
margin-right: auto;
}
.form-control {
width: 40%;
display: inline-block;
float: left;
font-family: 'Montserrat', sans-serif
}
.btn-sm {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
float: left;
border-radius: 3px;
margin-top: -33px;
margin-left: 80px;
font-family: 'Montserrat', sans-serif;
font-size: 14px;
}
.input-qty {
width: 45%;
float: right;
margin-left: 10px;
margin-top: -5px;
}
span.cartcount {
font-size: 24px;
font-weight: 700;
}
div.checkout-div > button {
margin-left: 49%;
display: block;
width: 250px;
font-family: montserrat, sans-serif;
background-color: #ef0000;
color: #ffffff;
font-weight: 700;
letter-spacing: 1.5px;
border-radius: 12px;
padding: 10px;
font-size: 18px;
}
.table-cart {
width: 100%;
max-width: 100%;
margin-bottom: 20px;
font-family: montserrat, sans-serif;
}
#instructions {
display: none;
}
#instructions > h2 > span {
float: left;
}
textarea {
width: 325px;
height: 200px;
}
#delinstruct {
background-color: white;
border: none;
border-radius: 24px;
cursor: pointer;
font-size: 16px;
padding: 16px 32px;
width: 170px;
background-color: #c21807;
color: #ffffff;
letter-spacing: 2px;
font-weight: 700;
margin: 20px 0 0 170px;
display: block;
margin-bottom: 20px;
}
#instructions > p {
font-family: montserrat, sans-serif;
}
#delinstruct:hover {
background-color: #f31e09;
}
#message {
margin-top: -45px;
width: 320px;
display: none;
padding: 10px;
font-weight: bold;
border-radius: 5px;
font-family: monserrat, sans-serif;
font-size: 18px;
animation: clear-message 3s 1;
-webkit-animation: clear-message 3s 1;
animation-fill-mode: forwards;
animation-delay: 30s;
-webkit-animation-delay: 1s;
/* Safari and Chrome */
-webkit-animation-fill-mode: forwards;
}
@keyframes clear-message {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@-webkit-keyframes clear-message {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
#message.success {
background-color: #ace1af;
color: #008000;
display: block;
}
#message.success > span {
float: left;
font-size: 30px;
color: #008000;
padding: 0px 10px;
line-height: 40px;
line-height: 50px;
}
#message.failure {
background-color: #FFD1DC;
color: #ff0000;
display: block;
}
#message.failure > span {
float: left;
font-size: 30px;
color: red;
padding: 0px 10px;
line-height: 40px;
line-height: 50px;
}
#message.info {
background-color: #FCF75E;
display: block;
color: #000000;
line-height: 20px;
}
#message.info > span {
float: left;
font-size: 30px;
color: #000000;
padding: 0px 10px;
line-height: 20px;
}
#app > div:nth-child(6) {
margin-bottom: 40px;
border-bottom: 1px solid #000000;
padding-bottom: 20px;
}
button {
display: inline-block;
border: none;
padding: 16px 32px;
margin: 0;
text-decoration: none;
background: #ffffff;
color: #000000;
font-family: sans-serif;
font-size: 32px;
line-height: 1;
cursor: pointer;
text-align: center;
transition: background 250ms ease-in-out, transform 150ms ease;
-webkit-appearance: none;
-moz-appearance: none;
margin-top: -10px;
}
button:hover,
button:focus {
background: #8b0000;
color: #ffffff;
}
button:focus {
outline: 1px solid #fff;
outline-offset: -4px;
}
button:active {
transform: scale(0.99);
}
.btn-default:hover {
color: #ffffff;
background-color: #8b0000;
border-color: #000000;
}
.btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover, .btn-default:focus {
background-color: #8b0000;
color: #ffffff;
border-color: #000000;
}
.btn.active.focus, .btn.active:focus, .btn.focus,
.btn:active.focus, .btn:active:focus, .btn:focus {
outline: none;
}
.btn-primary {
background-color: #ef0000;
border: 1px solid #8b0000;
outline: none;
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active,
.btn-primary:active:focus {
background-color: #8b0000;
border: 1px solid #8b0000;
outline: none;
}
.product {
display: flex;
font-family: 'Montserrat', sans-serif;
height: 40px;
}
.product > .price {
margin-left: 20px;
}
.producttitle {
width: 400px;
}
.carttotal, .cartant {
font-weight: bold;
font-size: 16px;
}
.cartamt:before {
content: "$";
}
td.blank {
width: 390px;
}
td.carttotal {
width: 172px;
}
.discard {
font-size: 18px;
width: 20px;
padding: 0;
margin-left: 10px;
margin-top: 0px;
}
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Amatic+SC&amp;display=swap" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment