"use strict";
window.fbmPayments && (window.fbmPayments.initForm = async function () {
var pay = window.fbmPayments,
paymentForm,
card,
headerImg = '/yokee/images/header-bg.jpg',
logoImg = '/yokee/images/app-logo-248-beveled.png',
closeImg = '/images/close-button-white.png',
anims = {};
async function onPaymentConfirmed(e) {
var d = e.detail || {},
action = pay.selectedAction;
if (paymentForm) {
paymentForm.removeEventListener('submit', onPaymentFormSubmit);
}
clearCache();
var body = {
intentId: d.intentId,
setupIntentId: d.setupIntentId,
subId: d.subId,
attribution: urlParamsAsObject()
};
Object.assign(body, browserInfo());
try {
var completeInfo = await pay.post('/complete', body);
if (completeInfo.voucher.isNew) {
var price = completeInfo.invoice.price;
pay.trackEvent('purchase', {
affiliation: pay.provider,
currency: (completeInfo.invoice.currency || '').toUpperCase(),
transaction_id: completeInfo.invoice.id,
value: price,
items: [{
id: action.priceId,
name: action.offer,
brand: action.brand,
variant: action.desc,
quantity: 1,
price
}]
});
}
if (!paymentForm) showForm({ completeInfo }); else showCompleteView(completeInfo);
} catch (err) {
pay.dispatchPaymentFailedEvent(err);
}
}
function showCompleteView(completeInfo) {
submitButtons().forEach(function(x) { x.remove() });
var mainElements = paymentForm.querySelectorAll('.main > :not(.complete)');
mainElements.forEach(function(x) { if (!x.classList.contains('complete')) x.remove(); })
paymentForm.querySelectorAll('.complete').forEach(function(e) { e.classList.toggle('hidden'); });
paymentForm.querySelectorAll('.activate').forEach(function(e) { e.href = completeInfo.voucher.url });
if (completeInfo.customer && completeInfo.customer.email) {
var msg;
if (isAppCompatible()) {
msg = 'Want to activate later?
The activation link was just sent to {{email}}. You can share or activate it at any time. Please note it can be activated only once.'.replace(
'{{email}}', completeInfo.customer.email
);
} else {
msg = 'An email has been sent to {{email}}. Open it from your mobile device to download and activate Yokee.'.replace(
'{{email}}', completeInfo.customer.email
);
}
setTimeout(function() {
paymentForm.querySelectorAll('.notice').forEach(function(e) {
e.innerHTML = msg;
});
}, 0);
}
paymentForm.querySelectorAll('.contact_link').forEach(function(e) {
var url = 'mailto:support@yokee.tv?subject=Purchase+Number+{{code}}'.replace(
'{{code}}', completeInfo.voucher.code
);
e.href = url;
});
paymentForm.querySelectorAll('.purchase_confirmation').forEach(function(e) {
var msg = 'Confirmation: {{code}}'.replace(
'{{code}}', completeInfo.voucher.code
);
e.innerHTML = msg;
});
var animElems = paymentForm.querySelectorAll('.anim');
for (var e of animElems) anims[e.id].play();
}
function onPaymentMethodVerified(e) {
var action = pay.selectedAction;
pay.trackEvent('checkout_progress', {
currency: (action.price.currency || '').toUpperCase(),
value: action.price.amount,
checkout_option: pay.provider,
checkout_step: pay.CHECKOUT_STEP_METHOD_VERIFIED,
items: [{
id: action.priceId,
name: action.offer,
brand: action.brand,
variant: action.desc,
quantity: 1,
price: action.price.amount
}]
});
createSubscription(e.detail);
}
function onPaymentRequiresAction(e) {
var d = e.detail || {},
action = pay.selectedAction;
pay.trackEvent('checkout_progress', {
currency: (action.price.currency || '').toUpperCase(),
value: action.price.amount,
checkout_option: pay.provider,
checkout_step: pay.CHECKOUT_STEP_ACTION_REQUIRED,
items: [{
id: action.priceId,
name: action.offer,
brand: action.brand,
variant: action.desc,
quantity: 1,
price: action.price.amount
}]
});
if (d.pendingSetupIntent && d.pendingSetupIntent.id) {
confirmPaymentSetup({
pendingSetupIntent: d.pendingSetupIntent,
subId: d.subId
})
} else {
confirmPayment({
paymentMethodId: d.paymentMethodId,
paymentIntent: d.paymentIntent
});
}
}
function onPaymentFailed(e) {
var d = e.detail,
err = d.error,
action = pay.selectedAction;
if (err.error) err = err.error;
var msg;
if (err.code === undefined) {
msg = 'Payment failed: {{message}}'.replace(
'{{message}}', err.message
);
} else {
msg = 'Payment failed: {{message}} ({{code}})'.replace(
'{{code}}', err.code
).replace(
'{{message}}', err.message
);
}
// https://developers.google.com/gtagjs/reference/event#exception
pay.trackEvent('exception', {
description: msg,
fatal: true
});
if (paymentForm) {
var errorElement = document.querySelector('#payment-error');
errorElement.textContent = msg;
toggleSubmitLoader();
} else {
pay.resetModal();
setTimeout(function () {
alert(msg);
}, 1);
}
}
async function createSubscription({ paymentMethodId, complete }) {
try {
var result = await pay.post('/sub', {
paymentMethodId, priceId: pay.selectedAction.priceId
});
await processCreateSubscriptionResult({ paymentMethodId, result });
if (complete) complete('success');
return Promise.resolve();
} catch (err) {
if (complete) complete('fail');
return pay.dispatchPaymentFailedEvent(err);
}
}
async function confirmPaymentSetup({ pendingSetupIntent, subId }) {
var psi = pendingSetupIntent;
try {
var res = await pay.stripe.confirmCardSetup(psi.client_secret);
// start code flow to handle updating the payment details
// Display error message in your UI.
// The card was declined (i.e. insufficient funds, card has expired, etc)
if (!res) throw new Error('missing setup response');
if (res.error) throw res.error; // error-like object
var si = res.setupIntent;
if (!si) throw new Error('missing setup info');
if (si.status !== 'succeeded') throw new Error(`invalid setup status ${si.status}`);
if (!si.id) throw new Error(`missing setup id`);
return pay.dispatchPaymentConfirmedEvent({ setupIntentId: si.id, subId });
} catch (err) {
return pay.dispatchPaymentFailedEvent(err);
}
}
async function confirmPayment({ paymentMethodId, paymentIntent }) {
var pmId = paymentMethodId;
var pi = paymentIntent;
try {
var res = await pay.stripe.confirmCardPayment(pi.client_secret, {
payment_method: pmId
})
// start code flow to handle updating the payment details
// Display error message in your UI.
// The card was declined (i.e. insufficient funds, card has expired, etc)
if (!res) throw new Error('missing payment response');
if (res.error) throw res.error; // error-like object
var pi = res.paymentIntent;
if (!pi) throw new Error('missing payment info');
if (pi.status !== 'succeeded') throw new Error(`invalid payment status ${pi.status}`);
if (!pi.id) throw new Error(`missing payment id`);
return pay.dispatchPaymentConfirmedEvent({ intentId: pi.id });
} catch (err) {
return pay.dispatchPaymentFailedEvent(err);
}
}
async function createPaymentMethod({ billingDetails, invoiceId, customerId }) {
try {
// invoiceId, customerId exist IFF we retry
var result = await pay.stripe.createPaymentMethod({
type: 'card',
card: card,
billing_details: billingDetails,
});
if (result.error) return pay.dispatchPaymentFailedEvent(result);
if (invoiceId && customerId) {
// Update the payment method and retry invoice payment
return retryInvoiceWithNewPaymentMethod({
paymentMethodId: result.paymentMethod.id,
customerId,
invoiceId
});
} else {
return pay.dispatchPaymentMethodVerifiedEvent({
paymentMethodId: result.paymentMethod.id
});
}
} catch(err) {
return pay.dispatchPaymentFailedEvent(err);
}
}
async function retryInvoiceWithNewPaymentMethod({ customerId, paymentMethodId, invoiceId }) {
var result = await pay.post('/retry', { customerId, paymentMethodId, invoiceId });
return processRetryInvoiceResult({ paymentMethodId, result });
}
async function processCreateSubscriptionResult({ paymentMethodId, result }) {
if (result.error) throw result;
var sub = result.subscription || {};
var pi = sub.payment_intent || {};
if (processActiveSubscription(sub, pi)) return;
// const subStatusToProcess = new Set(['active', 'trialing']);
// const intentStatusToProcess = new Set(['succeeded', '']);
// if (!subStatusToProcess.has(sub.status)) return false;
// if (!intentStatusToProcess.has(pi.status))
var psi = sub.pending_setup_intent || {};
if (pi.status === 'requires_confirmation') {
return confirmPayment({
paymentMethodId,
paymentIntent: pi
});
} else if (pi.status === 'requires_action' || psi.status === 'requires_confirmation') {
// additional authentication required by financial institution? (3DS, 2FA)
return pay.dispatchPaymentRequiresActionEvent({
paymentMethodId,
paymentIntent: pi,
pendingSetupIntent: psi,
subId: sub.id
});
} else if (pi.status === 'requires_payment_method') {
// Card attached to customer but charging the customer failed?
return processRequiresPaymentMethod({
invoiceId: sub.latest_invoice,
customerId: sub.customer.id,
paymentIntent: pi
});
}
throw new Error(`invalid inactive status ${pi.status}`);
}
function processRetryInvoiceResult({paymentMethodId, result }) {
if (result.error) throw result;
var sub = result.subscription || {};
var pi = sub.payment_intent || {};
if (processActiveSubscription(sub, pi)) return;
if (['requires_action', 'requires_payment_method'].indexOf(pi.status) >= 0) {
// additional authentication required or
// we need to re-confirm the previously-failed payment:
return pay.dispatchPaymentRequiresActionEvent({
paymentMethodId,
paymentIntent: pi
});
}
throw new Error(`invalid inactive status ${pi.status}`);
}
function processActiveSubscription(sub, pi) {
if (sub.status !== 'active') return false;
if (pi.status !== 'succeeded')
throw new Error(`invalid active status ${pi.status}`);
pay.dispatchPaymentConfirmedEvent({ intentId: pi.id });
return true;
}
function processRequiresPaymentMethod({ invoiceId, customerId, paymentIntent }) {
// Using localStorage to store the state of the retry: latest invoice ID and status
localStorage.setItem('latestInvoiceId', invoiceId);
localStorage.setItem('customerId', customerId);
localStorage.setItem(
'lastPaymentIntentStatus',
paymentIntent.status
);
throw new Error('Your card was declined');
}
function onPaymentFormSubmit(ev) {
ev.preventDefault();
clearOutcome();
toggleSubmitLoader();
var name = document.querySelector("#payment-name").value;
var email = document.querySelector("#payment-email").value;
var countryCode = document.querySelector("#payment-country").value;
try {
validateUserInput(name, email, countryCode);
} catch (err) {
return pay.dispatchPaymentFailedEvent(err);
}
var billingDetails = {
name: name,
email: email,
address: {
country: countryCode
}
};
var lastPaymentIntentStatus = localStorage.getItem('lastPaymentIntentStatus');
if (lastPaymentIntentStatus === 'requires_payment_method') {
// If a previous payment failed, reuse lastest invoice and customer
// with a new payment method:
var invoiceId = localStorage.getItem('latestInvoiceId');
var customerId = localStorage.getItem('customerId');
// create new payment method & retry payment on invoice with new payment method
createPaymentMethod({
billingDetails,
priceId: pay.selectedAction.priceId,
card, customerId, invoiceId
});
} else {
// create new payment method & create subscription
createPaymentMethod({
billingDetails,
priceId: pay.selectedAction.priceId,
card
});
}
}
function validateUserInput(name, email, countryCode) {
if (!name || name.trim().length == 0) throw new Error('Missing name');
if (!email || email.trim().length == 0) throw new Error('Missing email');
if (!countryCode || countryCode.trim().length == 0) throw new Error('Missing country');
}
function toggleSubmitLoader() {
var btnElems = submitButtons();
btnElems.forEach(function(btnElem) {
var textElem = btnElem.querySelector("span");
var loaderElem = btnElem.querySelector(".loader");
textElem.classList.toggle('hidden');
loaderElem.classList.toggle('hidden');
if (btnElem.getAttribute('disabled'))
btnElem.removeAttribute('disabled');
else
btnElem.setAttribute('disabled', 'disabled');
});
}
function submitButtons() {
return paymentForm.querySelectorAll("[type='submit']");
}
function initCardElement() {
var elements = pay.stripe.elements();
card = elements.create('card', {
iconStyle: 'solid',
style: {
base: {
iconColor: '#8898AA',
color: '#586A82',
lineHeight: '36px',
fontWeight: 300,
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSize: '19px',
'::placeholder': {
color: '#8898AA',
},
},
invalid: {
iconColor: '#e85746',
color: '#e85746',
}
},
classes: {
base: 'field',
focus: 'is-focused',
},
});
card.mount("#payment-card");
}
function showForm({ completeInfo }) {
buildForm();
setTimeout(function () {
paymentForm.style.visibility = 'visible';
document.getElementById("payment-email").focus();
if (completeInfo) showCompleteView(completeInfo);
}, 20); // fonts need some time to load, to avoid flicker
}
function buildForm() {
var modal = pay.modal();
modal.innerHTML = `