This commit is contained in:
David Remer 2020-04-02 08:38:00 +03:00
parent ba97d7143d
commit 779eb31e4e
53 changed files with 8475 additions and 0 deletions

View file

@ -0,0 +1,51 @@
import Renderer from './modules/Renderer';
import SingleProductConfig from './modules/SingleProductConfig';
import UpdateCart from './modules/UpdateCart';
import ErrorHandler from './modules/ErrorHandler';
document.addEventListener(
'DOMContentLoaded',
() => {
if (! typeof(PayPalCommerceGateway)) {
console.error('PayPal button could not be configured.');
return;
}
if (! document.querySelector(PayPalCommerceGateway.button.wrapper)) {
console.error('No wrapper for PayPal button found.');
return;
}
const context = PayPalCommerceGateway.context;
if (context === 'product' && ! document.querySelector('form.cart') ) {
return;
}
const errorHandler = new ErrorHandler();
const renderer = new Renderer({
url: PayPalCommerceGateway.button.url,
wrapper:PayPalCommerceGateway.button.wrapper
});
const updateCart = new UpdateCart(
PayPalCommerceGateway.ajax.change_cart.endpoint,
PayPalCommerceGateway.ajax.change_cart.nonce
);
let configurator = null;
if (context === 'product') {
configurator = new SingleProductConfig(
PayPalCommerceGateway,
updateCart,
renderer.showButtons.bind(renderer),
renderer.hideButtons.bind(renderer),
document.querySelector('form.cart'),
errorHandler
);
}
if (! configurator) {
console.error('No context for button found.');
return;
}
renderer.render(configurator.configuration());
}
);

View file

@ -0,0 +1,32 @@
/**
* When you can't add something to the cart, the PayPal buttons should not show.
* Therefore we listen for changes on the add to cart button and show/hide the buttons accordingly.
*/
class ButtonsToggleListener {
constructor(element, showCallback, hideCallback) {
this.element = element;
this.showCallback = showCallback;
this.hideCallback = hideCallback;
this.observer = null;
}
init() {
const config = { attributes : true };
const callback = () => {
if (this.element.classList.contains('disabled')) {
this.hideCallback();
return;
}
this.showCallback();
}
this.observer = new MutationObserver(callback);
this.observer.observe(this.element, config);
}
disconnect() {
this.observer.disconnect();
}
}
export default ButtonsToggleListener;

View file

@ -0,0 +1,27 @@
class ErrorHandler {
constructor() {
this.wrapper = document.querySelector('.woocommerce-notices-wrapper');
}
message(text) {
this.wrapper.classList.add('woocommerce-error');
this.wrapper.innerText = this.sanitize(text);
}
sanitize(text) {
const textarea = document.createElement('textarea');
textarea.innerHTML = text;
return textarea.value;
}
clear() {
if (! this.wrapper.classList.contains('woocommerce-error')) {
return;
}
this.wrapper.classList.remove('woocommerce-error');
this.wrapper.innerText = '';
}
}
export default ErrorHandler;

View file

@ -0,0 +1,39 @@
class Renderer {
constructor(config) {
this.config = config;
}
render(buttonConfig) {
const script = document.createElement('script');
if (typeof paypal !== 'object') {
script.setAttribute('src', this.config.url);
script.addEventListener('load', (event) => {
this.renderButtons(buttonConfig);
})
document.body.append(script);
return;
}
this.renderButtons(buttonConfig);
}
renderButtons(buttonConfig) {
paypal.Buttons(
buttonConfig
).render(this.config.wrapper);
}
hideButtons() {
document.querySelector(this.config.wrapper).style.display = 'none';
}
showButtons() {
document.querySelector(this.config.wrapper).style.display = 'block';
}
}
export default Renderer;

View file

@ -0,0 +1,95 @@
import ButtonsToggleListener from "./ButtonsToggleListener";
class SingleProductConfig {
constructor(
config,
updateCart,
showButtonCallback,
hideButtonCallback,
formElement,
errorHandler
) {
this.config = config;
this.updateCart = updateCart;
this.showButtonCallback = showButtonCallback;
this.hideButtonCallback = hideButtonCallback;
this.formElement = formElement;
this.errorHandler = errorHandler;
}
configuration() {
if ( this.hasVariations() ) {
const observer = new ButtonsToggleListener(
this.formElement.querySelector('.single_add_to_cart_button'),
this.showButtonCallback,
this.hideButtonCallback
);
observer.init();
}
const onApprove = (data, actions) => {
return actions.redirect(this.config.redirect);
}
return {
createOrder: this.createOrder(),
onApprove,
onError: (error) => {
this.errorHandler.message(error);
}
}
}
createOrder() {
const createOrder = (data, actions) => {
this.errorHandler.clear();
const product = document.querySelector('[name="add-to-cart"]').value;
const qty = document.querySelector('[name="quantity"]').value;
const variations = this.variations();
const onResolve = (purchase_units) => {
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
body: JSON.stringify({
nonce:this.config.ajax.create_order.nonce,
purchase_units
})
}).then(function(res) {
return res.json();
}).then(function(data) {
if (! data.success) {
//Todo: Error handling
return;
}
return data.data.id;
});
};
const promise = this.updateCart.update(onResolve, product, qty, variations);
return promise;
};
return createOrder;
}
variations() {
if (! this.hasVariations()) {
return null;
}
const attributes = [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return {
value:element.value,
name:element.name
}
}
);
return attributes;
}
hasVariations() {
return this.formElement.classList.contains('variations_form');
}
}
export default SingleProductConfig;

View file

@ -0,0 +1,39 @@
class UpdateCart {
constructor(endpoint, nonce) {
this.endpoint = endpoint;
this.nonce = nonce;
}
update(onResolve, product, qty, variations) {
return new Promise( (resolve, reject) => {
fetch(
this.endpoint,
{
method: 'POST',
body: JSON.stringify({
nonce: this.nonce,
product,
qty,
variations
})
}
).then(
(result) => {
return result.json();
}
).then( (result) => {
if (! result.success) {
reject(result.data);
return;
}
const resolved = onResolve(result.data);
resolve(resolved);
}
)
});
}
}
export default UpdateCart;