diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 4958d3648..993b115e3 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,7 +10,7 @@ "Edge >= 14" ], "dependencies": { - "@paypal/react-paypal-js": "^8.2.0", + "@paypal/react-paypal-js": "^8.3.0", "core-js": "^3.25.0", "react": "^17.0.0", "react-dom": "^17.0.0" diff --git a/modules/ppcp-blocks/resources/js/Components/card-fields.js b/modules/ppcp-blocks/resources/js/Components/card-fields.js new file mode 100644 index 000000000..f7ff6c7e0 --- /dev/null +++ b/modules/ppcp-blocks/resources/js/Components/card-fields.js @@ -0,0 +1,79 @@ +import {useEffect, useState} from '@wordpress/element'; + +import { + PayPalScriptProvider, + PayPalCardFieldsProvider, + PayPalCardFieldsForm, +} from "@paypal/react-paypal-js"; + +import {CheckoutHandler} from "./checkout-handler"; +import {createOrder, onApprove} from "../card-fields-config"; +import {cartHasSubscriptionProducts} from "../Helper/Subscription"; + +export function CardFields({config, eventRegistration, emitResponse, components}) { + const {onPaymentSetup} = eventRegistration; + const {responseTypes} = emitResponse; + const { PaymentMethodIcons } = components; + + const [cardFieldsForm, setCardFieldsForm] = useState(); + const getCardFieldsForm = (cardFieldsForm) => { + setCardFieldsForm(cardFieldsForm) + } + + const getSavePayment = (savePayment) => { + localStorage.setItem('ppcp-save-card-payment', savePayment); + } + + const hasSubscriptionProducts = cartHasSubscriptionProducts(config.scriptData); + + useEffect( + () => + onPaymentSetup(() => { + async function handlePaymentProcessing() { + await cardFieldsForm.submit() + .catch((error) => { + return { + type: responseTypes.ERROR, + } + }); + + return { + type: responseTypes.SUCCESS, + } + } + + return handlePaymentProcessing(); + }), + [onPaymentSetup, cardFieldsForm] + ); + + return ( + <> + + { + console.error(err); + }} + > + + + + + + + ) +} diff --git a/modules/ppcp-blocks/resources/js/Components/checkout-handler.js b/modules/ppcp-blocks/resources/js/Components/checkout-handler.js new file mode 100644 index 000000000..48dd0602d --- /dev/null +++ b/modules/ppcp-blocks/resources/js/Components/checkout-handler.js @@ -0,0 +1,28 @@ +import {useEffect} from '@wordpress/element'; +import {usePayPalCardFields} from "@paypal/react-paypal-js"; + +export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscriptionProducts, saveCardText, is_vaulting_enabled}) => { + const {cardFieldsForm} = usePayPalCardFields(); + + useEffect(() => { + getCardFieldsForm(cardFieldsForm) + }, []); + + if (!is_vaulting_enabled) { + return null; + } + + return ( + <> + getSavePayment(e.target.checked)} + defaultChecked={hasSubscriptionProducts} + disabled={hasSubscriptionProducts} + /> + + + ) +} diff --git a/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js b/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js new file mode 100644 index 000000000..6d268c122 --- /dev/null +++ b/modules/ppcp-blocks/resources/js/advanced-card-checkout-block.js @@ -0,0 +1,17 @@ +import { registerPaymentMethod } from '@woocommerce/blocks-registry'; +import {CardFields} from "./Components/card-fields"; + +const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); + +registerPaymentMethod({ + name: config.id, + label:
, + content: , + edit:
, + ariaLabel: config.title, + canMakePayment: () => {return true}, + supports: { + showSavedCards: true, + features: config.supports + } +}) diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js new file mode 100644 index 000000000..37d939a5f --- /dev/null +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -0,0 +1,44 @@ +const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data'); + +export async function createOrder() { + return fetch(config.scriptData.ajax.create_order.endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nonce: config.scriptData.ajax.create_order.nonce, + context: config.scriptData.context, + payment_method: 'ppcp-credit-card-gateway', + save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true', + }), + }) + .then((response) => response.json()) + .then((order) => { + return order.data.id; + }) + .catch((err) => { + console.error(err); + }); +} + +export async function onApprove(data) { + return fetch(config.scriptData.ajax.approve_order.endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + order_id: data.orderID, + nonce: config.scriptData.ajax.approve_order.nonce, + }), + }) + .then((response) => response.json()) + .then((data) => { + console.log(data) + localStorage.removeItem('ppcp-save-card-payment'); + }) + .catch((err) => { + console.error(err); + }); +} diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index a841e97b9..21a97f984 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; +use WooCommerce\PayPalCommerce\Blocks\Endpoint\GetPayPalOrderFromSession; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; @@ -45,6 +46,17 @@ return array( $container->get( 'wcgateway.all-funding-sources' ) ); }, + 'blocks.advanced-card-method' => static function( ContainerInterface $container ): AdvancedCardPaymentMethod { + return new AdvancedCardPaymentMethod( + $container->get( 'blocks.url' ), + $container->get( 'ppcp.asset-version' ), + $container->get( 'wcgateway.credit-card-gateway' ), + function () use ( $container ): SmartButtonInterface { + return $container->get( 'button.smart-button' ); + }, + $container->get( 'wcgateway.settings' ) + ); + }, 'blocks.settings.final_review_enabled' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof ContainerInterface ); diff --git a/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php new file mode 100644 index 000000000..2c6fd60f4 --- /dev/null +++ b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php @@ -0,0 +1,142 @@ +name = CreditCardGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->gateway = $gateway; + $this->smart_button = $smart_button; + $this->settings = $settings; + } + + /** + * {@inheritDoc} + */ + public function initialize() {} + + /** + * {@inheritDoc} + */ + public function is_active() { + return true; + } + + /** + * {@inheritDoc} + */ + public function get_payment_method_script_handles() { + wp_register_script( + 'ppcp-advanced-card-checkout-block', + trailingslashit( $this->module_url ) . 'assets/js/advanced-card-checkout-block.js', + array(), + $this->version, + true + ); + + return array( 'ppcp-advanced-card-checkout-block' ); + } + + /** + * {@inheritDoc} + */ + public function get_payment_method_data() { + $script_data = $this->smart_button_instance()->script_data(); + + return array( + 'id' => $this->name, + 'title' => $this->gateway->title, + 'description' => $this->gateway->description, + 'scriptData' => $script_data, + 'supports' => $this->gateway->supports, + 'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ), + 'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ), + 'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(), + ); + } + + /** + * The smart button. + * + * @return SmartButtonInterface + */ + private function smart_button_instance(): SmartButtonInterface { + if ( $this->smart_button instanceof SmartButtonInterface ) { + return $this->smart_button; + } + + if ( is_callable( $this->smart_button ) ) { + $this->smart_button = ( $this->smart_button )(); + } + + return $this->smart_button; + } +} diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 2599e75fa..1dd1560dd 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -61,6 +61,7 @@ class BlocksModule implements ModuleInterface { 'woocommerce_blocks_payment_method_type_registration', function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void { $payment_method_registry->register( $c->get( 'blocks.method' ) ); + $payment_method_registry->register( $c->get( 'blocks.advanced-card-method' ) ); } ); diff --git a/modules/ppcp-blocks/webpack.config.js b/modules/ppcp-blocks/webpack.config.js index bdf508fba..44ab3e68f 100644 --- a/modules/ppcp-blocks/webpack.config.js +++ b/modules/ppcp-blocks/webpack.config.js @@ -10,6 +10,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin() ], entry: { 'checkout-block': path.resolve('./resources/js/checkout-block.js'), + 'advanced-card-checkout-block': path.resolve('./resources/js/advanced-card-checkout-block.js'), "gateway": path.resolve('./resources/css/gateway.scss') }, output: { diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index ff731f5b7..1e812a4ae 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1005,19 +1005,19 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@paypal/paypal-js@^8.0.4": - version "8.0.4" - resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.4.tgz#abe9f40f519b1d2c306adddfbe733be03eb26ce5" - integrity sha512-91g5fhRBHGEBoikDzQT6uBn3PzlJQ75g0c3MvqVJqN0XRm5kHa9wz+6+Uaq8QQuxRzz5C2x55Zg057CW6EuwpQ== +"@paypal/paypal-js@^8.0.5": + version "8.0.5" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.5.tgz#77bc461b4d1e5a2c6f081269e3ef0b2e3331a68c" + integrity sha512-yQNV7rOILeaVCNU4aVDRPqEnbIlzfxgQfFsxzsBuZW1ouqRD/4kYBWJDzczCiscSr2xOeA/Pkm7e3a9fRfnuMQ== dependencies: promise-polyfill "^8.3.0" -"@paypal/react-paypal-js@^8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.2.0.tgz#4b1a142bbb68e62dca4a92da4a6b5568f54901f0" - integrity sha512-SworUfu0BNNcqoh0O53Ke4MFpx2m3qJRu3hayXvlluEEXJpKqGSV5aaSGFhbsZqi8hnbsx/hZR7BQbmqsggiGQ== +"@paypal/react-paypal-js@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.3.0.tgz#a103080b752766b8ff59b8620887abf802e1a01b" + integrity sha512-SX17d2h1CMNFGI+wtjb329AEDaBR8Ziy2LCV076eDcY1Q0MFKRkfQ/v0HOAvZtk3sJoydRmYez2pq47BRblwqQ== dependencies: - "@paypal/paypal-js" "^8.0.4" + "@paypal/paypal-js" "^8.0.5" "@paypal/sdk-constants" "^1.0.122" "@paypal/sdk-constants@^1.0.122": diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 7d6909c2e..7925633cc 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1421,7 +1421,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $disable_funding, array_diff( array_keys( $this->all_funding_sources ), - array( 'venmo', 'paylater', 'paypal' ) + array( 'venmo', 'paylater', 'paypal', 'card' ) ) ); } diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php index 6835108c3..b499891a0 100644 --- a/modules/ppcp-card-fields/src/CardFieldsModule.php +++ b/modules/ppcp-card-fields/src/CardFieldsModule.php @@ -105,9 +105,8 @@ class CardFieldsModule implements ModuleInterface { add_filter( 'ppcp_create_order_request_body_data', - function( array $data ) use ( $c ): array { + function( array $data, string $payment_method ) use ( $c ): array { // phpcs:ignore WordPress.Security.NonceVerification.Missing - $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); if ( $payment_method !== CreditCardGateway::ID ) { return $data; } @@ -134,7 +133,9 @@ class CardFieldsModule implements ModuleInterface { } return $data; - } + }, + 10, + 2 ); } }