init hosted fields functionality

This commit is contained in:
David Remer 2020-04-30 15:28:48 +03:00
parent 1225ad83c8
commit 23dd407fc8
21 changed files with 212 additions and 45 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,13 @@
import MiniCartBootstap from './modules/MiniCartBootstap'; import MiniCartBootstap from './modules/ContextBootstrap/MiniCartBootstap';
import SingleProductBootstap from './modules/SingleProductBootstap'; import SingleProductBootstap from './modules/ContextBootstrap/SingleProductBootstap';
import CartBootstrap from './modules/CartBootstap'; import CartBootstrap from './modules/ContextBootstrap/CartBootstap';
import CheckoutBootstap from './modules/CheckoutBootstap'; import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap';
import Renderer from './modules/Renderer'; import Renderer from './modules/Renderer/Renderer';
import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer";
const bootstrap = () => { const bootstrap = () => {
const renderer = new Renderer(PayPalCommerceGateway); const creditCardRenderer = new CreditCardRenderer(PayPalCommerceGateway);
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway);
const context = PayPalCommerceGateway.context; const context = PayPalCommerceGateway.context;
if (context === 'mini-cart' || context === 'product') { if (context === 'mini-cart' || context === 'product') {
@ -55,6 +57,11 @@ document.addEventListener(
const script = document.createElement('script'); const script = document.createElement('script');
script.setAttribute('src', PayPalCommerceGateway.button.url); script.setAttribute('src', PayPalCommerceGateway.button.url);
Object.entries(PayPalCommerceGateway.script_attributes).forEach(
(keyValue) => {
script.setAttribute(keyValue[0], keyValue[1]);
}
);
script.addEventListener('load', (event) => { script.addEventListener('load', (event) => {
bootstrap(); bootstrap();
}); });

View file

@ -1,5 +1,5 @@
import onApprove from './onApproveForContinue.js'; import onApprove from '../OnApproveHandler/onApproveForContinue.js';
import {payerData} from "./Payer"; import {payerData} from "../Helper/PayerData";
class CartActionHandler { class CartActionHandler {

View file

@ -1,5 +1,5 @@
import onApprove from './onApproveForPayNow.js'; import onApprove from '../OnApproveHandler/onApproveForPayNow.js';
import {payerData} from "./Payer"; import {payerData} from "../Helper/PayerData";
class CheckoutActionHandler { class CheckoutActionHandler {

View file

@ -1,7 +1,7 @@
import ButtonsToggleListener from './ButtonsToggleListener'; import ButtonsToggleListener from '../Helper/ButtonsToggleListener';
import Product from './Product'; import Product from '../Entity/Product';
import onApprove from './onApproveForContinue'; import onApprove from '../OnApproveHandler/onApproveForContinue';
import {payerData} from "./Payer"; import {payerData} from "../Helper/PayerData";
class SingleProductActionHandler { class SingleProductActionHandler {

View file

@ -1,5 +1,5 @@
import CartActionHandler from './CartActionHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler';
import ErrorHandler from './ErrorHandler'; import ErrorHandler from '../ErrorHandler';
class CartBootstrap { class CartBootstrap {
constructor(gateway, renderer) { constructor(gateway, renderer) {
@ -31,6 +31,7 @@ class CartBootstrap {
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, this.gateway.button.wrapper,
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(), actionHandler.configuration(),
); );
} }

View file

@ -1,5 +1,5 @@
import ErrorHandler from './ErrorHandler'; import ErrorHandler from '../ErrorHandler';
import CheckoutActionHandler from './CheckoutActionHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
class CheckoutBootstap { class CheckoutBootstap {
constructor(gateway, renderer) { constructor(gateway, renderer) {
@ -22,6 +22,7 @@ class CheckoutBootstap {
on('updated_checkout payment_method_selected', () => { on('updated_checkout payment_method_selected', () => {
this.switchBetweenPayPalandOrderButton(); this.switchBetweenPayPalandOrderButton();
}); });
this.switchBetweenPayPalandOrderButton();
} }
shouldRender() { shouldRender() {
@ -40,6 +41,7 @@ class CheckoutBootstap {
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, this.gateway.button.wrapper,
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(), actionHandler.configuration(),
); );
} }
@ -50,10 +52,12 @@ class CheckoutBootstap {
if (currentPaymentMethod !== 'ppcp-gateway') { if (currentPaymentMethod !== 'ppcp-gateway') {
this.renderer.hideButtons(this.gateway.button.wrapper); this.renderer.hideButtons(this.gateway.button.wrapper);
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
jQuery('#place_order').show(); jQuery('#place_order').show();
} }
else { else {
this.renderer.showButtons(this.gateway.button.wrapper); this.renderer.showButtons(this.gateway.button.wrapper);
this.renderer.showButtons(this.gateway.hosted_fields.wrapper);
jQuery('#place_order').hide(); jQuery('#place_order').hide();
} }
} }

View file

@ -1,5 +1,5 @@
import ErrorHandler from './ErrorHandler'; import ErrorHandler from '../ErrorHandler';
import CartActionHandler from './CartActionHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler';
class MiniCartBootstap { class MiniCartBootstap {
constructor(gateway, renderer) { constructor(gateway, renderer) {
@ -32,6 +32,7 @@ class MiniCartBootstap {
this.renderer.render( this.renderer.render(
this.gateway.button.mini_cart_wrapper, this.gateway.button.mini_cart_wrapper,
null,
actionHandler.configuration() actionHandler.configuration()
); );
} }

View file

@ -1,6 +1,6 @@
import ErrorHandler from './ErrorHandler'; import ErrorHandler from '../ErrorHandler';
import UpdateCart from './UpdateCart'; import UpdateCart from "../Helper/UpdateCart";
import SingleProductActionHandler from './SingleProductActionHandler'; import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
class SingleProductBootstap { class SingleProductBootstap {
constructor(gateway, renderer) { constructor(gateway, renderer) {
@ -43,6 +43,7 @@ class SingleProductBootstap {
this.renderer.render( this.renderer.render(
this.gateway.button.wrapper, this.gateway.button.wrapper,
this.gateway.hosted_fields.wrapper,
actionHandler.configuration(), actionHandler.configuration(),
); );
} }

View file

@ -1,4 +1,4 @@
import Product from "./Product"; import Product from "../Entity/Product";
class UpdateCart { class UpdateCart {
constructor(endpoint, nonce) constructor(endpoint, nonce)

View file

@ -0,0 +1,49 @@
class CreditCardRenderer {
constructor(defaultConfig) {
this.defaultConfig = defaultConfig;
}
render(wrapper, contextConfig) {
if (
wrapper === null
|| typeof paypal.HostedFields === 'undefined'
|| ! paypal.HostedFields.isEligible()
|| document.querySelector(wrapper) === null
) {
return;
}
//ToDo: Styles
paypal.HostedFields.render({
createOrder: contextConfig.createOrder,
fields: {
number: {
selector: '#ppcp-credit-card',
placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number,
},
cvv: {
selector: '#ppcp-cvv',
placeholder: this.defaultConfig.hosted_fields.labels.cvv,
},
expirationDate: {
selector: '#ppcp-expiration-date',
placeholder: this.defaultConfig.hosted_fields.labels.mm_yyyy,
}
}
}).then(hostedFields => {
document.querySelector(wrapper).addEventListener(
'submit',
event => {
event.preventDefault();
hostedFields.submit().then(payload => {
payload.orderID = payload.orderId;
return contextConfig.onApprove(payload);
});
}
);
});
}
}
export default CreditCardRenderer;

View file

@ -1,9 +1,10 @@
class Renderer { class Renderer {
constructor(defaultConfig) { constructor(creditCardRenderer, defaultConfig) {
this.defaultConfig = defaultConfig; this.defaultConfig = defaultConfig;
this.creditCardRenderer = creditCardRenderer;
} }
render(wrapper, contextConfig) { render(wrapper, hostedFieldsWrapper, contextConfig) {
if (this.isAlreadyRendered(wrapper)) { if (this.isAlreadyRendered(wrapper)) {
return; return;
} }
@ -13,7 +14,9 @@ class Renderer {
style, style,
...contextConfig, ...contextConfig,
}).render(wrapper); }).render(wrapper);
}
this.creditCardRenderer.render(hostedFieldsWrapper, contextConfig);
}
isAlreadyRendered(wrapper) { isAlreadyRendered(wrapper) {
return document.querySelector(wrapper).hasChildNodes(); return document.querySelector(wrapper).hasChildNodes();

View file

@ -16,17 +16,21 @@ use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
return [ return [
'button.smart-button' => static function (ContainerInterface $container): SmartButtonInterface { 'button.smart-button' => static function (ContainerInterface $container): SmartButtonInterface {
$settings = $container->get('wcgateway.settings'); $settings = $container->get('wcgateway.settings');
$payeeRepository = $container->get('api.repository.payee'); if (! wc_string_to_bool($settings->get('enabled'))) {
if (wc_string_to_bool($settings->get('enabled'))) { return new DisabledSmartButton();
return new SmartButton(
$container->get('button.url'),
$container->get('session.handler'),
$settings,
$payeeRepository
);
} }
return new DisabledSmartButton(); $payeeRepository = $container->get('api.repository.payee');
$identityToken = $container->get('api.endpoint.identity-token');
return new SmartButton(
$container->get('button.url'),
$container->get('session.handler'),
$settings,
$payeeRepository,
$identityToken
);
}, },
'button.url' => static function (ContainerInterface $container): string { 'button.url' => static function (ContainerInterface $container): string {
return plugins_url( return plugins_url(

View file

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets; namespace Inpsyde\PayPalCommerce\Button\Assets;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\IdentityToken;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository; use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
@ -17,24 +19,39 @@ class SmartButton implements SmartButtonInterface
private $sessionHandler; private $sessionHandler;
private $settings; private $settings;
private $payeeRepository; private $payeeRepository;
private $identityToken;
public function __construct( public function __construct(
string $moduleUrl, string $moduleUrl,
SessionHandler $sessionHandler, SessionHandler $sessionHandler,
Settings $settings, Settings $settings,
PayeeRepository $payeeRepository PayeeRepository $payeeRepository,
IdentityToken $identityToken
) { ) {
$this->moduleUrl = $moduleUrl; $this->moduleUrl = $moduleUrl;
$this->sessionHandler = $sessionHandler; $this->sessionHandler = $sessionHandler;
$this->settings = $settings; $this->settings = $settings;
$this->payeeRepository = $payeeRepository; $this->payeeRepository = $payeeRepository;
$this->identityToken = $identityToken;
} }
public function renderWrapper(): bool public function renderWrapper(): bool
{ {
$renderer = static function () {
$hostedFieldsEnabled = $this->dccIsEnabled();
$renderer = static function () use ($hostedFieldsEnabled) {
echo '<div id="ppc-button"></div>'; echo '<div id="ppc-button"></div>';
if (! $hostedFieldsEnabled) {
return;
}
printf(
'<form id="ppc-hosted-fields"><label for="ppcp-credit-card">%s</label><div id="ppcp-credit-card"></div><label for="ppcp-expiration-date">%s</label><div id="ppcp-expiration-date"></div><label for="ppcp-cvv">%s</label><div id="ppcp-cvv"></div><button>%s</button></form>',
__('Card number', 'woocommerce-paypal-commerce-gateway'),
__('Expiration Date', 'woocommerce-paypal-commerce-gateway'),
__('CVV', 'woocommerce-paypal-commerce-gateway'),
__('Pay with Card', 'woocommerce-paypal-commerce-gateway')
);
}; };
if (is_cart() && wc_string_to_bool($this->settings->get('button_cart_enabled'))) { if (is_cart() && wc_string_to_bool($this->settings->get('button_cart_enabled'))) {
add_action( add_action(
@ -88,6 +105,7 @@ class SmartButton implements SmartButtonInterface
private function localizeScript(): array private function localizeScript(): array
{ {
$localize = [ $localize = [
'script_attributes' => $this->attributes(),
'redirect' => wc_get_checkout_url(), 'redirect' => wc_get_checkout_url(),
'context' => $this->context(), 'context' => $this->context(),
'ajax' => [ 'ajax' => [
@ -117,6 +135,14 @@ class SmartButton implements SmartButtonInterface
'label' => 'paypal', 'label' => 'paypal',
], ],
], ],
'hosted_fields' => [
'wrapper' => '#ppc-hosted-fields',
'labels' => [
'credit_card_number' => __('Credit Card Number', 'woocommerce-paypal-commerce-gateway'),
'cvv' => __('CVV', 'woocommerce-paypal-commerce-gateway'),
'mm_yyyy' => __('MM/YYYY', 'woocommerce-paypal-commerce-gateway'),
],
],
]; ];
return $localize; return $localize;
} }
@ -155,18 +181,21 @@ class SmartButton implements SmartButtonInterface
{ {
$params = [ $params = [
//ToDo: Add the correct client id, toggle when settings is set to sandbox //ToDo: Add the correct client id, toggle when settings is set to sandbox
'client-id' => 'AcVzowpNCpTxFzLG7onQI4JD0sVcA0BkZv-D42qRZPv_gZ8cNfX9zGL_8bXmSu7cbJ5B2DH7sot8vDpw', 'client-id' => 'AQB97CzMsd58-It1vxbcDAGvMuXNCXRD9le_XUaMlHB_U7XsU9IiItBwGQOtZv9sEeD6xs2vlIrL4NiD',
'currency' => get_woocommerce_currency(), 'currency' => get_woocommerce_currency(),
'locale' => get_user_locale(), 'locale' => get_user_locale(),
//'debug' => (defined('WP_DEBUG') && WP_DEBUG) ? 'true' : 'false', //'debug' => (defined('WP_DEBUG') && WP_DEBUG) ? 'true' : 'false',
//ToDo: Update date on releases. //ToDo: Update date on releases.
'integration-date' => date('Y-m-d'), 'integration-date' => date('Y-m-d'),
'components' => 'marks,buttons', 'components' => implode(',', $this->components()),
//ToDo: Probably only needed, when DCC //ToDo: Probably only needed, when DCC
'vault' => 'false', 'vault' => $this->dccIsEnabled() ? 'false' : 'false',
'commit' => is_checkout() ? 'true' : 'false', 'commit' => is_checkout() ? 'true' : 'false',
'intent' => $this->settings->get('intent'), 'intent' => $this->settings->get('intent'),
]; ];
if (defined('WP_DEBUG') && \WP_DEBUG && WC()->customer) {
$params['buyer-country'] = WC()->customer->get_billing_country();
}
$payee = $this->payeeRepository->payee(); $payee = $this->payeeRepository->payee();
if ($payee->merchantId()) { if ($payee->merchantId()) {
$params['merchant-id'] = $payee->merchantId(); $params['merchant-id'] = $payee->merchantId();
@ -179,6 +208,28 @@ class SmartButton implements SmartButtonInterface
return $smartButtonUrl; return $smartButtonUrl;
} }
private function attributes() : array {
$attributes = [
//'data-partner-attribution-id' => '',
];
try {
$clientToken = $this->identityToken->generate();
$attributes['data-client-token'] = $clientToken->token();
return $attributes;
} catch (RuntimeException $exception) {
return $attributes;
}
}
private function components() : array
{
$components = ['buttons'];
if ($this->dccIsEnabled()) {
$components[] = 'hosted-fields';
}
return $components;
}
private function context(): string private function context(): string
{ {
$context = 'mini-cart'; $context = 'mini-cart';
@ -193,4 +244,9 @@ class SmartButton implements SmartButtonInterface
} }
return $context; return $context;
} }
private function dccIsEnabled() : bool
{
return wc_string_to_bool($this->settings->get('enable_dcc'));
}
} }

View file

@ -14,10 +14,11 @@ class SettingsFields
$this->gateway(), $this->gateway(),
$this->account(), $this->account(),
$this->buttons(), $this->buttons(),
$this->creditCards(),
); );
} }
protected function gateway(): array private function gateway(): array
{ {
return [ return [
'enabled' => [ 'enabled' => [
@ -91,7 +92,7 @@ class SettingsFields
{ {
return [ return [
'button_settings' => [ 'button_settings' => [
'title' => __('Button Settings', 'woocommerce-paypal-gateway'), 'title' => __('SmartButton Settings', 'woocommerce-paypal-gateway'),
'type' => 'title', 'type' => 'title',
'description' => __( 'description' => __(
'Customize the appearance of PayPal Payments on your site.', 'Customize the appearance of PayPal Payments on your site.',
@ -174,4 +175,44 @@ class SettingsFields
], ],
]; ];
} }
private function creditCards() : array {
return [
'credit_card_settings' => [
'title' => __('Credit Card Settings', 'woocommerce-paypal-gateway'),
'type' => 'title',
'description' => __(
'Customize the appearance of Credit Card Payments on your site.',
'woocommerce-paypal-gateway'
),
],
'enable_dcc' => [
'title' => __('Enable credit card payment', 'woocommerce-paypal-gateway'),
'type' => 'checkbox',
'label' => __('Enable credit card payments.', 'woocommerce-paypal-gateway'),
'default' => 'yes',
],
'disable_cards' => [
'title' => __('Disable specific credid cards', 'woocommerce-paypal-gateway'),
'type' => 'multiselect',
'class' => 'wc-enhanced-select',
'default' => [],
'desc_tip' => true,
'description' => __(
'By default all possible credit cards will be shown. You can disable some cards, if you wish.',
'woocommerce-paypal-gateway'
),
'options' => [
'visa' => _x('Visa', 'Name of credit card', 'woocommerce-paypal-gateway'),
'mastercard' => _x('Mastercard', 'Name of credit card', 'woocommerce-paypal-gateway'),
'amex' => _x('American Express', 'Name of credit card', 'woocommerce-paypal-gateway'),
'discover' => _x('Discover', 'Name of credit card', 'woocommerce-paypal-gateway'),
'jcb' => _x('JCB', 'Name of credit card', 'woocommerce-paypal-gateway'),
'elo' => _x('Elo', 'Name of credit card', 'woocommerce-paypal-gateway'),
'hiper' => _x('Hiper', 'Name of credit card', 'woocommerce-paypal-gateway'),
],
],
];
}
} }