mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
🔀 Merge branch 'trunk'
# Conflicts: # modules/ppcp-settings/resources/js/data/common/action-types.js # modules/ppcp-settings/resources/js/data/common/controls.js # modules/ppcp-settings/resources/js/data/common/hooks.js # modules/ppcp-settings/services.php
This commit is contained in:
commit
9a84c7b4a9
52 changed files with 2318 additions and 1462 deletions
|
@ -27,7 +27,7 @@ web_environment:
|
||||||
- ADMIN_USER=admin
|
- ADMIN_USER=admin
|
||||||
- ADMIN_PASS=admin
|
- ADMIN_PASS=admin
|
||||||
- ADMIN_EMAIL=admin@example.com
|
- ADMIN_EMAIL=admin@example.com
|
||||||
- WC_VERSION=7.7.2
|
- WC_VERSION=9.5.1
|
||||||
|
|
||||||
# Key features of ddev's config.yaml:
|
# Key features of ddev's config.yaml:
|
||||||
|
|
||||||
|
|
2
.github/workflows/package.yml
vendored
2
.github/workflows/package.yml
vendored
|
@ -50,7 +50,7 @@ jobs:
|
||||||
if: github.event.inputs.filePrefix
|
if: github.event.inputs.filePrefix
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.FILENAME }}
|
name: ${{ env.FILENAME }}
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
*** Changelog ***
|
*** Changelog ***
|
||||||
|
|
||||||
|
= 2.9.6 - XXXX-XX-XX =
|
||||||
|
* Fix - NOT_ENABLED_TO_VAULT_PAYMENT_SOURCE on PayPal transactions when using ACDC Vaulting without PayPal Vault approval #2955
|
||||||
|
* Fix - Express buttons for Free Trial Subscription products on Block Cart/Checkout trigger CANNOT_BE_ZERO_OR_NEGATIVE error #2872
|
||||||
|
* Fix - String translations not applied to Card Fields on Block Checkout #2934
|
||||||
|
* Fix - Fastlane component included in script when Fastlane is disabled #2911
|
||||||
|
* Fix - Zero amount line items may trigger CANNOT_BE_ZERO_OR_NEGATIVE error after rounding error #2906
|
||||||
|
* Fix - “Save changes” is grey and unclickable when switching from Sandbox to Live #2895
|
||||||
|
* Fix - plugin queries variations when button/messaging is disabled on single product page #2896
|
||||||
|
* Fix - Use get_id instead of get_order_number on setting custom_id (author @0verscore) #2930
|
||||||
|
* Enhancement - Improve fraud response order notes for Advanced Card Processing transactions #2905
|
||||||
|
* Tweak - Update the minimum plugin requirements to WordPress 6.5 & WooCommerce 9.2 #2920
|
||||||
|
|
||||||
= 2.9.5 - 2024-12-10 =
|
= 2.9.5 - 2024-12-10 =
|
||||||
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
* Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
||||||
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
* Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
||||||
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
* Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
||||||
Fix - "Voide authorization" button does not appear for Apple Pay/Google Pay orders when payment buttons are separated #2752
|
* Fix - "Voide authorization" button does not appear for Apple Pay/Google Pay orders when payment buttons are separated #2752
|
||||||
Fix - Additional payment tokens saved with new customer_id #2820
|
* Fix - Additional payment tokens saved with new customer_id #2820
|
||||||
Fix - Vaulted payment method may not be displayed in PayPal button for return buyer #2809
|
* Fix - Vaulted payment method may not be displayed in PayPal button for return buyer #2809
|
||||||
Fix - Conflict with EasyShip plugin due to shipping methods loading too early #2845
|
* Fix - Conflict with EasyShip plugin due to shipping methods loading too early #2845
|
||||||
Fix - Restore accidentally removed ACDC currencies #2838
|
* Fix - Restore accidentally removed ACDC currencies #2838
|
||||||
Enhancement - Native gateway icon for PayPal & Pay upon Invoice gateways #2712
|
* Enhancement - Native gateway icon for PayPal & Pay upon Invoice gateways #2712
|
||||||
Enhancement - Allow disabling specific card types for Fastlane #2704
|
* Enhancement - Allow disabling specific card types for Fastlane #2704
|
||||||
Enhancement - Fastlane Insights SDK implementation for block Checkout #2737
|
* Enhancement - Fastlane Insights SDK implementation for block Checkout #2737
|
||||||
Enhancement - Hide split local APMs in Payments settings tab when PayPal is not enabled #2703
|
* Enhancement - Hide split local APMs in Payments settings tab when PayPal is not enabled #2703
|
||||||
Enhancement - Do not load split local APMs on Checkout when PayPal is not enabled #2792
|
* Enhancement - Do not load split local APMs on Checkout when PayPal is not enabled #2792
|
||||||
Enhancement - Add support for Button Options in the Block Checkout for Apple Pay & Google Pay buttons #2797 #2772
|
* Enhancement - Add support for Button Options in the Block Checkout for Apple Pay & Google Pay buttons #2797 #2772
|
||||||
Enhancement - Disable “Add payment method” button while saving ACDC payment #2794
|
* Enhancement - Disable “Add payment method” button while saving ACDC payment #2794
|
||||||
Enhancement - Sanitize soft_descriptor field #2846 #2854
|
* Enhancement - Sanitize soft_descriptor field #2846 #2854
|
||||||
|
|
||||||
= 2.9.4 - 2024-11-11 =
|
= 2.9.4 - 2024-11-11 =
|
||||||
* Fix - Apple Pay button preview missing in Standard payment and Advanced Processing tabs #2755
|
* Fix - Apple Pay button preview missing in Standard payment and Advanced Processing tabs #2755
|
||||||
|
|
|
@ -17,44 +17,44 @@ class FraudProcessorResponse {
|
||||||
/**
|
/**
|
||||||
* The AVS response code.
|
* The AVS response code.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $avs_code;
|
protected string $avs_code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The CVV response code.
|
* The CVV response code.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cvv_code;
|
protected string $cvv2_code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FraudProcessorResponse constructor.
|
* FraudProcessorResponse constructor.
|
||||||
*
|
*
|
||||||
* @param string|null $avs_code The AVS response code.
|
* @param string|null $avs_code The AVS response code.
|
||||||
* @param string|null $cvv_code The CVV response code.
|
* @param string|null $cvv2_code The CVV response code.
|
||||||
*/
|
*/
|
||||||
public function __construct( ?string $avs_code, ?string $cvv_code ) {
|
public function __construct( ?string $avs_code, ?string $cvv2_code ) {
|
||||||
$this->avs_code = $avs_code;
|
$this->avs_code = (string) $avs_code;
|
||||||
$this->cvv_code = $cvv_code;
|
$this->cvv2_code = (string) $cvv2_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the AVS response code.
|
* Returns the AVS response code.
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function avs_code(): ?string {
|
public function avs_code(): string {
|
||||||
return $this->avs_code;
|
return $this->avs_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the CVV response code.
|
* Returns the CVV response code.
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function cvv_code(): ?string {
|
public function cvv_code(): string {
|
||||||
return $this->cvv_code;
|
return $this->cvv2_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,11 +64,99 @@ class FraudProcessorResponse {
|
||||||
*/
|
*/
|
||||||
public function to_array(): array {
|
public function to_array(): array {
|
||||||
return array(
|
return array(
|
||||||
'avs_code' => $this->avs_code() ?: '',
|
'avs_code' => $this->avs_code(),
|
||||||
|
'cvv2_code' => $this->cvv_code(),
|
||||||
|
// For backwards compatibility.
|
||||||
'address_match' => $this->avs_code() === 'M' ? 'Y' : 'N',
|
'address_match' => $this->avs_code() === 'M' ? 'Y' : 'N',
|
||||||
'postal_match' => $this->avs_code() === 'M' ? 'Y' : 'N',
|
'postal_match' => $this->avs_code() === 'M' ? 'Y' : 'N',
|
||||||
'cvv_match' => $this->cvv_code() === 'M' ? 'Y' : 'N',
|
'cvv_match' => $this->cvv_code() === 'M' ? 'Y' : 'N',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the AVS (Address Verification System) code messages based on the AVS response code.
|
||||||
|
*
|
||||||
|
* Provides human-readable descriptions for various AVS response codes
|
||||||
|
* and returns the corresponding message for the given code.
|
||||||
|
*
|
||||||
|
* @return string The AVS response code message. If the code is not found, an error message is returned.
|
||||||
|
*/
|
||||||
|
public function get_avs_code_message(): string {
|
||||||
|
if ( ! $this->avs_code() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$messages = array(
|
||||||
|
/* Visa, Mastercard, Discover, American Express */
|
||||||
|
'A' => 'A: Address - Address only (no ZIP code)',
|
||||||
|
'B' => 'B: International "A" - Address only (no ZIP code)',
|
||||||
|
'C' => 'C: International "N" - None. The transaction is declined.',
|
||||||
|
'D' => 'D: International "X" - Address and Postal Code',
|
||||||
|
'E' => 'E: Not allowed for MOTO (Internet/Phone) transactions - Not applicable. The transaction is declined.',
|
||||||
|
'F' => 'F: UK-specific "X" - Address and Postal Code',
|
||||||
|
'G' => 'G: Global Unavailable - Not applicable',
|
||||||
|
'I' => 'I: International Unavailable - Not applicable',
|
||||||
|
'M' => 'M: Address - Address and Postal Code',
|
||||||
|
'N' => 'N: No - None. The transaction is declined.',
|
||||||
|
'P' => 'P: Postal (International "Z") - Postal Code only (no Address)',
|
||||||
|
'R' => 'R: Retry - Not applicable',
|
||||||
|
'S' => 'S: Service not Supported - Not applicable',
|
||||||
|
'U' => 'U: Unavailable / Address not checked, or acquirer had no response. Service not available.',
|
||||||
|
'W' => 'W: Whole ZIP - Nine-digit ZIP code (no Address)',
|
||||||
|
'X' => 'X: Exact match - Address and nine-digit ZIP code)',
|
||||||
|
'Y' => 'Y: Yes - Address and five-digit ZIP',
|
||||||
|
'Z' => 'Z: ZIP - Five-digit ZIP code (no Address)',
|
||||||
|
/* Maestro */
|
||||||
|
'0' => '0: All the address information matched.',
|
||||||
|
'1' => '1: None of the address information matched. The transaction is declined.',
|
||||||
|
'2' => '2: Part of the address information matched.',
|
||||||
|
'3' => '3: The merchant did not provide AVS information. Not processed.',
|
||||||
|
'4' => '4: Address not checked, or acquirer had no response. Service not available.',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Psalm suppress
|
||||||
|
*
|
||||||
|
* @psalm-suppress PossiblyNullArrayOffset
|
||||||
|
* @psalm-suppress PossiblyNullArgument
|
||||||
|
*/
|
||||||
|
return $messages[ $this->avs_code() ] ?? sprintf( '%s: Error', $this->avs_code() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the CVV2 code message based on the CVV code provided.
|
||||||
|
*
|
||||||
|
* This method maps CVV response codes to their corresponding descriptive messages.
|
||||||
|
*
|
||||||
|
* @return string The descriptive message corresponding to the CVV2 code, or a formatted error message if the code is unrecognized.
|
||||||
|
*/
|
||||||
|
public function get_cvv2_code_message(): string {
|
||||||
|
if ( ! $this->cvv_code() ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$messages = array(
|
||||||
|
/* Visa, Mastercard, Discover, American Express */
|
||||||
|
'E' => 'E: Error - Unrecognized or Unknown response',
|
||||||
|
'I' => 'I: Invalid or Null',
|
||||||
|
'M' => 'M: Match or CSC',
|
||||||
|
'N' => 'N: No match',
|
||||||
|
'P' => 'P: Not processed',
|
||||||
|
'S' => 'S: Service not supported',
|
||||||
|
'U' => 'U: Unknown - Issuer is not certified',
|
||||||
|
'X' => 'X: No response / Service not available',
|
||||||
|
/* Maestro */
|
||||||
|
'0' => '0: Matched CVV2',
|
||||||
|
'1' => '1: No match',
|
||||||
|
'2' => '2: The merchant has not implemented CVV2 code handling',
|
||||||
|
'3' => '3: Merchant has indicated that CVV2 is not present on card',
|
||||||
|
'4' => '4: Service not available',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Psalm suppress
|
||||||
|
*
|
||||||
|
* @psalm-suppress PossiblyNullArrayOffset
|
||||||
|
* @psalm-suppress PossiblyNullArgument
|
||||||
|
*/
|
||||||
|
return $messages[ $this->cvv_code() ] ?? sprintf( '%s: Error', $this->cvv_code() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,11 @@ class PurchaseUnitSanitizer {
|
||||||
// Get a more intelligent adjustment mechanism.
|
// Get a more intelligent adjustment mechanism.
|
||||||
$increment = ( new MoneyFormatter() )->minimum_increment( $item['unit_amount']['currency_code'] );
|
$increment = ( new MoneyFormatter() )->minimum_increment( $item['unit_amount']['currency_code'] );
|
||||||
|
|
||||||
|
// not floor items that will be negative then.
|
||||||
|
if ( (float) $item['unit_amount']['value'] < $increment ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$this->purchase_unit['items'][ $index ]['unit_amount'] = ( new Money(
|
$this->purchase_unit['items'][ $index ]['unit_amount'] = ( new Money(
|
||||||
( (float) $item['unit_amount']['value'] ) - $increment,
|
( (float) $item['unit_amount']['value'] ) - $increment,
|
||||||
$item['unit_amount']['currency_code']
|
$item['unit_amount']['currency_code']
|
||||||
|
|
|
@ -92,7 +92,10 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule
|
||||||
*/
|
*/
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_sdk_components_hook',
|
'woocommerce_paypal_payments_sdk_components_hook',
|
||||||
function( $components ) {
|
function( $components ) use ( $c ) {
|
||||||
|
if ( ! $c->has( 'axo.available' ) || ! $c->get( 'axo.available' ) ) {
|
||||||
|
return $components;
|
||||||
|
}
|
||||||
$components[] = 'fastlane';
|
$components[] = 'fastlane';
|
||||||
return $components;
|
return $components;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,9 @@ return array(
|
||||||
|
|
||||||
// If AXO is configured and onboarded.
|
// If AXO is configured and onboarded.
|
||||||
'axo.available' => static function ( ContainerInterface $container ): bool {
|
'axo.available' => static function ( ContainerInterface $container ): bool {
|
||||||
return true;
|
$settings = $container->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
return $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' );
|
||||||
},
|
},
|
||||||
|
|
||||||
'axo.url' => static function ( ContainerInterface $container ): string {
|
'axo.url' => static function ( ContainerInterface $container ): string {
|
||||||
|
|
|
@ -246,7 +246,13 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
*/
|
*/
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_sdk_components_hook',
|
'woocommerce_paypal_payments_sdk_components_hook',
|
||||||
function( $components ) {
|
function( $components ) use ( $c ) {
|
||||||
|
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
|
||||||
|
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
|
||||||
|
|
||||||
|
if ( ! $dcc_configuration->use_fastlane() ) {
|
||||||
|
return $components;
|
||||||
|
}
|
||||||
$components[] = 'fastlane';
|
$components[] = 'fastlane';
|
||||||
return $components;
|
return $components;
|
||||||
}
|
}
|
||||||
|
@ -255,14 +261,18 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
add_action(
|
add_action(
|
||||||
'wp_head',
|
'wp_head',
|
||||||
function () use ( $c ) {
|
function () use ( $c ) {
|
||||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
|
|
||||||
echo '<script async src="https://www.paypalobjects.com/insights/v1/paypal-insights.sandbox.min.js"></script>';
|
|
||||||
|
|
||||||
// Add meta tag to allow feature-detection of the site's AXO payment state.
|
// Add meta tag to allow feature-detection of the site's AXO payment state.
|
||||||
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
|
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
|
||||||
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
|
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
|
||||||
|
|
||||||
$this->add_feature_detection_tag( $dcc_configuration->use_fastlane() );
|
if ( $dcc_configuration->use_fastlane() ) {
|
||||||
|
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
|
||||||
|
echo '<script async src="https://www.paypalobjects.com/insights/v1/paypal-insights.sandbox.min.js"></script>';
|
||||||
|
|
||||||
|
$this->add_feature_detection_tag( true );
|
||||||
|
} else {
|
||||||
|
$this->add_feature_detection_tag( false );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
import { normalizeStyleForFundingSource } from '../../../../ppcp-button/resources/js/modules/Helper/Style';
|
||||||
|
import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js';
|
||||||
|
|
||||||
|
export const BlockEditorPayPalComponent = ( {
|
||||||
|
config,
|
||||||
|
fundingSource,
|
||||||
|
buttonAttributes,
|
||||||
|
} ) => {
|
||||||
|
const urlParams = useMemo(
|
||||||
|
() => ( {
|
||||||
|
clientId: 'test',
|
||||||
|
...config.scriptData.url_params,
|
||||||
|
dataNamespace: 'ppcp-blocks-editor-paypal-buttons',
|
||||||
|
components: 'buttons',
|
||||||
|
} ),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const style = useMemo( () => {
|
||||||
|
const configStyle = normalizeStyleForFundingSource(
|
||||||
|
config.scriptData.button.style,
|
||||||
|
fundingSource
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( buttonAttributes ) {
|
||||||
|
return {
|
||||||
|
...configStyle,
|
||||||
|
height: buttonAttributes.height
|
||||||
|
? Number( buttonAttributes.height )
|
||||||
|
: configStyle.height,
|
||||||
|
borderRadius: buttonAttributes.borderRadius
|
||||||
|
? Number( buttonAttributes.borderRadius )
|
||||||
|
: configStyle.borderRadius,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return configStyle;
|
||||||
|
}, [ fundingSource, buttonAttributes ] );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PayPalScriptProvider options={ urlParams }>
|
||||||
|
<PayPalButtons
|
||||||
|
className={ `ppc-button-container-${ fundingSource }` }
|
||||||
|
fundingSource={ fundingSource }
|
||||||
|
style={ style }
|
||||||
|
forceReRender={ [ buttonAttributes || {} ] }
|
||||||
|
onClick={ () => false }
|
||||||
|
/>
|
||||||
|
</PayPalScriptProvider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -3,10 +3,10 @@ import { useEffect, useState } from '@wordpress/element';
|
||||||
import {
|
import {
|
||||||
PayPalScriptProvider,
|
PayPalScriptProvider,
|
||||||
PayPalCardFieldsProvider,
|
PayPalCardFieldsProvider,
|
||||||
PayPalNameField,
|
PayPalNameField,
|
||||||
PayPalNumberField,
|
PayPalNumberField,
|
||||||
PayPalExpiryField,
|
PayPalExpiryField,
|
||||||
PayPalCVVField,
|
PayPalCVVField,
|
||||||
} from '@paypal/react-paypal-js';
|
} from '@paypal/react-paypal-js';
|
||||||
|
|
||||||
import { CheckoutHandler } from './checkout-handler';
|
import { CheckoutHandler } from './checkout-handler';
|
||||||
|
@ -19,11 +19,7 @@ import {
|
||||||
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
|
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
export function CardFields( {
|
export function CardFields( { config, eventRegistration, emitResponse } ) {
|
||||||
config,
|
|
||||||
eventRegistration,
|
|
||||||
emitResponse,
|
|
||||||
} ) {
|
|
||||||
const { onPaymentSetup } = eventRegistration;
|
const { onPaymentSetup } = eventRegistration;
|
||||||
const { responseTypes } = emitResponse;
|
const { responseTypes } = emitResponse;
|
||||||
|
|
||||||
|
@ -96,16 +92,36 @@ export function CardFields( {
|
||||||
console.error( err );
|
console.error( err );
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
<PayPalNameField placeholder={ __( 'Cardholder Name (optional)', 'woocommerce-paypal-payments' ) }/>
|
<PayPalNameField
|
||||||
<PayPalNumberField placeholder={ __( 'Card number', 'woocommerce-paypal-payments' ) }/>
|
placeholder={ __(
|
||||||
<div style={ { display: "flex", width: "100%" } }>
|
'Cardholder Name (optional)',
|
||||||
<div style={ { width: "100%" } }>
|
'woocommerce-paypal-payments'
|
||||||
<PayPalExpiryField placeholder={ __( 'MM / YY', 'woocommerce-paypal-payments' ) }/>
|
) }
|
||||||
</div>
|
/>
|
||||||
<div style={ { width: "100%" } }>
|
<PayPalNumberField
|
||||||
<PayPalCVVField placeholder={ __( 'CVV', 'woocommerce-paypal-payments' ) }/>
|
placeholder={ __(
|
||||||
</div>
|
'Card number',
|
||||||
</div>
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
<div style={ { display: 'flex', width: '100%' } }>
|
||||||
|
<div style={ { width: '100%' } }>
|
||||||
|
<PayPalExpiryField
|
||||||
|
placeholder={ __(
|
||||||
|
'MM / YY',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={ { width: '100%' } }>
|
||||||
|
<PayPalCVVField
|
||||||
|
placeholder={ __(
|
||||||
|
'CVV',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<CheckoutHandler
|
<CheckoutHandler
|
||||||
getCardFieldsForm={ getCardFieldsForm }
|
getCardFieldsForm={ getCardFieldsForm }
|
||||||
getSavePayment={ getSavePayment }
|
getSavePayment={ getSavePayment }
|
||||||
|
|
14
modules/ppcp-blocks/resources/js/Components/paypal-label.js
Normal file
14
modules/ppcp-blocks/resources/js/Components/paypal-label.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export const PaypalLabel = ( { components, config } ) => {
|
||||||
|
const { PaymentMethodIcons } = components;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: config.title,
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
<PaymentMethodIcons icons={ config.icon } align="right" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
493
modules/ppcp-blocks/resources/js/Components/paypal.js
Normal file
493
modules/ppcp-blocks/resources/js/Components/paypal.js
Normal file
|
@ -0,0 +1,493 @@
|
||||||
|
import { useEffect, useState } from '@wordpress/element';
|
||||||
|
import { loadPayPalScript } from '../../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||||
|
import {
|
||||||
|
mergeWcAddress,
|
||||||
|
paypalAddressToWc,
|
||||||
|
paypalOrderToWcAddresses,
|
||||||
|
} from '../Helper/Address';
|
||||||
|
import { convertKeysToSnakeCase } from '../Helper/Helper';
|
||||||
|
import buttonModuleWatcher from '../../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
||||||
|
import { normalizeStyleForFundingSource } from '../../../../ppcp-button/resources/js/modules/Helper/Style';
|
||||||
|
import {
|
||||||
|
cartHasSubscriptionProducts,
|
||||||
|
isPayPalSubscription,
|
||||||
|
} from '../Helper/Subscription';
|
||||||
|
import {
|
||||||
|
createOrder,
|
||||||
|
createSubscription,
|
||||||
|
createVaultSetupToken,
|
||||||
|
handleApprove,
|
||||||
|
handleApproveSubscription,
|
||||||
|
onApproveSavePayment,
|
||||||
|
} from '../paypal-config';
|
||||||
|
|
||||||
|
const PAYPAL_GATEWAY_ID = 'ppcp-gateway';
|
||||||
|
|
||||||
|
const namespace = 'ppcpBlocksPaypalExpressButtons';
|
||||||
|
let registeredContext = false;
|
||||||
|
let paypalScriptPromise = null;
|
||||||
|
|
||||||
|
export const PayPalComponent = ( {
|
||||||
|
config,
|
||||||
|
onClick,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
eventRegistration,
|
||||||
|
emitResponse,
|
||||||
|
activePaymentMethod,
|
||||||
|
shippingData,
|
||||||
|
isEditing,
|
||||||
|
fundingSource,
|
||||||
|
buttonAttributes,
|
||||||
|
} ) => {
|
||||||
|
const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } =
|
||||||
|
eventRegistration;
|
||||||
|
const { responseTypes } = emitResponse;
|
||||||
|
|
||||||
|
const [ paypalOrder, setPaypalOrder ] = useState( null );
|
||||||
|
const [ continuationFilled, setContinuationFilled ] = useState( false );
|
||||||
|
const [ gotoContinuationOnError, setGotoContinuationOnError ] =
|
||||||
|
useState( false );
|
||||||
|
|
||||||
|
const [ paypalScriptLoaded, setPaypalScriptLoaded ] = useState( false );
|
||||||
|
|
||||||
|
if ( ! paypalScriptLoaded ) {
|
||||||
|
if ( ! paypalScriptPromise ) {
|
||||||
|
// for editor, since canMakePayment was not called
|
||||||
|
paypalScriptPromise = loadPayPalScript(
|
||||||
|
namespace,
|
||||||
|
config.scriptData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
paypalScriptPromise.then( () => setPaypalScriptLoaded( true ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodId = fundingSource
|
||||||
|
? `${ config.id }-${ fundingSource }`
|
||||||
|
: config.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The block cart displays express checkout buttons. Those buttons are handled by the
|
||||||
|
* PAYPAL_GATEWAY_ID method on the server ("PayPal Smart Buttons").
|
||||||
|
*
|
||||||
|
* A possible bug in WooCommerce does not use the correct payment method ID for the express
|
||||||
|
* payment buttons inside the cart, but sends the ID of the _first_ active payment method.
|
||||||
|
*
|
||||||
|
* This function uses an internal WooCommerce dispatcher method to set the correct method ID.
|
||||||
|
*/
|
||||||
|
const enforcePaymentMethodForCart = () => {
|
||||||
|
// Do nothing, unless we're handling block cart express payment buttons.
|
||||||
|
if ( 'cart-block' !== config.scriptData.context ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the active payment method to PAYPAL_GATEWAY_ID.
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/payment' )
|
||||||
|
.__internalSetActivePaymentMethod( PAYPAL_GATEWAY_ID, {} );
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
// fill the form if in continuation (for product or mini-cart buttons)
|
||||||
|
if ( continuationFilled || ! config.scriptData.continuation?.order ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paypalAddresses = paypalOrderToWcAddresses(
|
||||||
|
config.scriptData.continuation.order
|
||||||
|
);
|
||||||
|
const wcAddresses = wp.data
|
||||||
|
.select( 'wc/store/cart' )
|
||||||
|
.getCustomerData();
|
||||||
|
const addresses = mergeWcAddress( wcAddresses, paypalAddresses );
|
||||||
|
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setBillingAddress( addresses.billingAddress );
|
||||||
|
|
||||||
|
if ( shippingData.needsShipping ) {
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setShippingAddress( addresses.shippingAddress );
|
||||||
|
}
|
||||||
|
} catch ( err ) {
|
||||||
|
// sometimes the PayPal address is missing, skip in this case.
|
||||||
|
console.error( err );
|
||||||
|
}
|
||||||
|
|
||||||
|
// this useEffect should run only once, but adding this in case of some kind of full re-rendering
|
||||||
|
setContinuationFilled( true );
|
||||||
|
}, [ shippingData, continuationFilled ] );
|
||||||
|
|
||||||
|
const getCheckoutRedirectUrl = () => {
|
||||||
|
const checkoutUrl = new URL( config.scriptData.redirect );
|
||||||
|
// sometimes some browsers may load some kind of cached version of the page,
|
||||||
|
// so adding a parameter to avoid that
|
||||||
|
checkoutUrl.searchParams.append(
|
||||||
|
'ppcp-continuation-redirect',
|
||||||
|
new Date().getTime().toString()
|
||||||
|
);
|
||||||
|
return checkoutUrl.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
const unsubscribe = onCheckoutValidation( () => {
|
||||||
|
if ( config.scriptData.continuation ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gotoContinuationOnError &&
|
||||||
|
wp.data.select( 'wc/store/validation' ).hasValidationErrors()
|
||||||
|
) {
|
||||||
|
location.href = getCheckoutRedirectUrl();
|
||||||
|
return { type: responseTypes.ERROR };
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} );
|
||||||
|
return unsubscribe;
|
||||||
|
}, [ onCheckoutValidation, gotoContinuationOnError ] );
|
||||||
|
|
||||||
|
const handleClick = ( data, actions ) => {
|
||||||
|
if ( isEditing ) {
|
||||||
|
return actions.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ppcpFundingSource = data.fundingSource;
|
||||||
|
|
||||||
|
onClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldHandleShippingInPayPal = () => {
|
||||||
|
return shouldskipFinalConfirmation() && config.needShipping;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldskipFinalConfirmation = () => {
|
||||||
|
if ( config.finalReviewEnabled ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
window.ppcpFundingSource !== 'venmo' ||
|
||||||
|
! config.scriptData.vaultingEnabled
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handleShippingOptionsChange = null;
|
||||||
|
let handleShippingAddressChange = null;
|
||||||
|
|
||||||
|
if ( shippingData.needsShipping && shouldHandleShippingInPayPal() ) {
|
||||||
|
handleShippingOptionsChange = async ( data, actions ) => {
|
||||||
|
try {
|
||||||
|
const shippingOptionId = data.selectedShippingOption?.id;
|
||||||
|
if ( shippingOptionId ) {
|
||||||
|
await wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.selectShippingRate( shippingOptionId );
|
||||||
|
await shippingData.setSelectedRates( shippingOptionId );
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.ajax.update_shipping.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
} catch ( e ) {
|
||||||
|
console.error( e );
|
||||||
|
|
||||||
|
actions.reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleShippingAddressChange = async ( data, actions ) => {
|
||||||
|
try {
|
||||||
|
const address = paypalAddressToWc(
|
||||||
|
convertKeysToSnakeCase( data.shippingAddress )
|
||||||
|
);
|
||||||
|
|
||||||
|
await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
||||||
|
shipping_address: address,
|
||||||
|
} );
|
||||||
|
|
||||||
|
await shippingData.setShippingAddress( address );
|
||||||
|
|
||||||
|
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.ajax.update_shipping.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
} catch ( e ) {
|
||||||
|
console.error( e );
|
||||||
|
|
||||||
|
actions.reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( activePaymentMethod !== methodId ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribeProcessing = onPaymentSetup( () => {
|
||||||
|
if (
|
||||||
|
cartHasSubscriptionProducts( config.scriptData ) &&
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: responseTypes.SUCCESS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( config.scriptData.continuation ) {
|
||||||
|
return {
|
||||||
|
type: responseTypes.SUCCESS,
|
||||||
|
meta: {
|
||||||
|
paymentMethodData: {
|
||||||
|
paypal_order_id:
|
||||||
|
config.scriptData.continuation.order_id,
|
||||||
|
funding_source:
|
||||||
|
window.ppcpFundingSource ?? 'paypal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const addresses = paypalOrderToWcAddresses( paypalOrder );
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: responseTypes.SUCCESS,
|
||||||
|
meta: {
|
||||||
|
paymentMethodData: {
|
||||||
|
paypal_order_id: paypalOrder.id,
|
||||||
|
funding_source: window.ppcpFundingSource ?? 'paypal',
|
||||||
|
},
|
||||||
|
...addresses,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
return () => {
|
||||||
|
unsubscribeProcessing();
|
||||||
|
};
|
||||||
|
}, [ onPaymentSetup, paypalOrder, activePaymentMethod ] );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( activePaymentMethod !== methodId ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unsubscribe = onCheckoutFail( ( { processingResponse } ) => {
|
||||||
|
console.error( processingResponse );
|
||||||
|
if ( onClose ) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
if ( config.scriptData.continuation ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( shouldskipFinalConfirmation() ) {
|
||||||
|
location.href = getCheckoutRedirectUrl();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} );
|
||||||
|
return unsubscribe;
|
||||||
|
}, [ onCheckoutFail, onClose, activePaymentMethod ] );
|
||||||
|
|
||||||
|
if ( config.scriptData.continuation ) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: config.scriptData.continuation.cancel.html,
|
||||||
|
} }
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! registeredContext ) {
|
||||||
|
buttonModuleWatcher.registerContextBootstrap(
|
||||||
|
config.scriptData.context,
|
||||||
|
{
|
||||||
|
createOrder: ( data ) => {
|
||||||
|
return createOrder( data, config, onError, onClose );
|
||||||
|
},
|
||||||
|
onApprove: ( data, actions ) => {
|
||||||
|
return handleApprove(
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
config,
|
||||||
|
shouldHandleShippingInPayPal,
|
||||||
|
shippingData,
|
||||||
|
setPaypalOrder,
|
||||||
|
shouldskipFinalConfirmation,
|
||||||
|
getCheckoutRedirectUrl,
|
||||||
|
setGotoContinuationOnError,
|
||||||
|
enforcePaymentMethodForCart,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
onClose
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
registeredContext = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = normalizeStyleForFundingSource(
|
||||||
|
config.scriptData.button.style,
|
||||||
|
fundingSource
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( typeof buttonAttributes !== 'undefined' ) {
|
||||||
|
style.height = buttonAttributes?.height
|
||||||
|
? Number( buttonAttributes.height )
|
||||||
|
: style.height;
|
||||||
|
style.borderRadius = buttonAttributes?.borderRadius
|
||||||
|
? Number( buttonAttributes.borderRadius )
|
||||||
|
: style.borderRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! paypalScriptLoaded ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PayPalButton = ppcpBlocksPaypalExpressButtons.Buttons.driver(
|
||||||
|
'react',
|
||||||
|
{ React, ReactDOM }
|
||||||
|
);
|
||||||
|
|
||||||
|
const getOnShippingOptionsChange = ( fundingSource ) => {
|
||||||
|
if ( fundingSource === 'venmo' ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( data, actions ) => {
|
||||||
|
shouldHandleShippingInPayPal()
|
||||||
|
? handleShippingOptionsChange( data, actions )
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOnShippingAddressChange = ( fundingSource ) => {
|
||||||
|
if ( fundingSource === 'venmo' ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( data, actions ) => {
|
||||||
|
const shippingAddressChange = shouldHandleShippingInPayPal()
|
||||||
|
? handleShippingAddressChange( data, actions )
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return shippingAddressChange;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
cartHasSubscriptionProducts( config.scriptData ) &&
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<PayPalButton
|
||||||
|
style={ style }
|
||||||
|
onClick={ handleClick }
|
||||||
|
onCancel={ onClose }
|
||||||
|
onError={ onClose }
|
||||||
|
createVaultSetupToken={ () => createVaultSetupToken( config ) }
|
||||||
|
onApprove={ ( { vaultSetupToken } ) =>
|
||||||
|
onApproveSavePayment( vaultSetupToken, config, onSubmit )
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isPayPalSubscription( config.scriptData ) ) {
|
||||||
|
return (
|
||||||
|
<PayPalButton
|
||||||
|
fundingSource={ fundingSource }
|
||||||
|
style={ style }
|
||||||
|
onClick={ handleClick }
|
||||||
|
onCancel={ onClose }
|
||||||
|
onError={ onClose }
|
||||||
|
createSubscription={ ( data, actions ) =>
|
||||||
|
createSubscription( data, actions, config )
|
||||||
|
}
|
||||||
|
onApprove={ ( data, actions ) =>
|
||||||
|
handleApproveSubscription(
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
config,
|
||||||
|
shouldHandleShippingInPayPal,
|
||||||
|
shippingData,
|
||||||
|
setPaypalOrder,
|
||||||
|
shouldskipFinalConfirmation,
|
||||||
|
getCheckoutRedirectUrl,
|
||||||
|
setGotoContinuationOnError,
|
||||||
|
enforcePaymentMethodForCart,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
onClose
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShippingOptionsChange={ getOnShippingOptionsChange(
|
||||||
|
fundingSource
|
||||||
|
) }
|
||||||
|
onShippingAddressChange={ getOnShippingAddressChange(
|
||||||
|
fundingSource
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PayPalButton
|
||||||
|
fundingSource={ fundingSource }
|
||||||
|
style={ style }
|
||||||
|
onClick={ handleClick }
|
||||||
|
onCancel={ onClose }
|
||||||
|
onError={ onClose }
|
||||||
|
createOrder={ ( data ) =>
|
||||||
|
createOrder( data, config, onError, onClose )
|
||||||
|
}
|
||||||
|
onApprove={ ( data, actions ) =>
|
||||||
|
handleApprove(
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
config,
|
||||||
|
shouldHandleShippingInPayPal,
|
||||||
|
shippingData,
|
||||||
|
setPaypalOrder,
|
||||||
|
shouldskipFinalConfirmation,
|
||||||
|
getCheckoutRedirectUrl,
|
||||||
|
setGotoContinuationOnError,
|
||||||
|
enforcePaymentMethodForCart,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
onClose
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShippingOptionsChange={ getOnShippingOptionsChange(
|
||||||
|
fundingSource
|
||||||
|
) }
|
||||||
|
onShippingAddressChange={ getOnShippingAddressChange(
|
||||||
|
fundingSource
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,750 +1,26 @@
|
||||||
import { useEffect, useState, useMemo } from '@wordpress/element';
|
|
||||||
import {
|
import {
|
||||||
registerExpressPaymentMethod,
|
registerExpressPaymentMethod,
|
||||||
registerPaymentMethod,
|
registerPaymentMethod,
|
||||||
} from '@woocommerce/blocks-registry';
|
} from '@woocommerce/blocks-registry';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import {
|
|
||||||
mergeWcAddress,
|
|
||||||
paypalAddressToWc,
|
|
||||||
paypalOrderToWcAddresses,
|
|
||||||
paypalSubscriptionToWcAddresses,
|
|
||||||
} from './Helper/Address';
|
|
||||||
import { convertKeysToSnakeCase } from './Helper/Helper';
|
|
||||||
import {
|
import {
|
||||||
cartHasSubscriptionProducts,
|
cartHasSubscriptionProducts,
|
||||||
isPayPalSubscription,
|
isPayPalSubscription,
|
||||||
} from './Helper/Subscription';
|
} from './Helper/Subscription';
|
||||||
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||||
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
|
|
||||||
import { normalizeStyleForFundingSource } from '../../../ppcp-button/resources/js/modules/Helper/Style';
|
|
||||||
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
|
||||||
import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBootstrap';
|
import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBootstrap';
|
||||||
|
import { PayPalComponent } from './Components/paypal';
|
||||||
|
import { BlockEditorPayPalComponent } from './Components/block-editor-paypal';
|
||||||
|
import { PaypalLabel } from './Components/paypal-label';
|
||||||
const namespace = 'ppcpBlocksPaypalExpressButtons';
|
const namespace = 'ppcpBlocksPaypalExpressButtons';
|
||||||
const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
|
const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
|
||||||
|
|
||||||
window.ppcpFundingSource = config.fundingSource;
|
window.ppcpFundingSource = config.fundingSource;
|
||||||
|
|
||||||
let registeredContext = false;
|
|
||||||
let paypalScriptPromise = null;
|
let paypalScriptPromise = null;
|
||||||
|
|
||||||
const PAYPAL_GATEWAY_ID = 'ppcp-gateway';
|
|
||||||
|
|
||||||
const PayPalComponent = ( {
|
|
||||||
onClick,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
onError,
|
|
||||||
eventRegistration,
|
|
||||||
emitResponse,
|
|
||||||
activePaymentMethod,
|
|
||||||
shippingData,
|
|
||||||
isEditing,
|
|
||||||
fundingSource,
|
|
||||||
buttonAttributes,
|
|
||||||
} ) => {
|
|
||||||
const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } =
|
|
||||||
eventRegistration;
|
|
||||||
const { responseTypes } = emitResponse;
|
|
||||||
|
|
||||||
const [ paypalOrder, setPaypalOrder ] = useState( null );
|
|
||||||
const [ continuationFilled, setContinuationFilled ] = useState( false );
|
|
||||||
const [ gotoContinuationOnError, setGotoContinuationOnError ] =
|
|
||||||
useState( false );
|
|
||||||
|
|
||||||
const [ paypalScriptLoaded, setPaypalScriptLoaded ] = useState( false );
|
|
||||||
|
|
||||||
if ( ! paypalScriptLoaded ) {
|
|
||||||
if ( ! paypalScriptPromise ) {
|
|
||||||
// for editor, since canMakePayment was not called
|
|
||||||
paypalScriptPromise = loadPayPalScript(
|
|
||||||
namespace,
|
|
||||||
config.scriptData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
paypalScriptPromise.then( () => setPaypalScriptLoaded( true ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
const methodId = fundingSource
|
|
||||||
? `${ config.id }-${ fundingSource }`
|
|
||||||
: config.id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The block cart displays express checkout buttons. Those buttons are handled by the
|
|
||||||
* PAYPAL_GATEWAY_ID method on the server ("PayPal Smart Buttons").
|
|
||||||
*
|
|
||||||
* A possible bug in WooCommerce does not use the correct payment method ID for the express
|
|
||||||
* payment buttons inside the cart, but sends the ID of the _first_ active payment method.
|
|
||||||
*
|
|
||||||
* This function uses an internal WooCommerce dispatcher method to set the correct method ID.
|
|
||||||
*/
|
|
||||||
const enforcePaymentMethodForCart = () => {
|
|
||||||
// Do nothing, unless we're handling block cart express payment buttons.
|
|
||||||
if ( 'cart-block' !== config.scriptData.context ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the active payment method to PAYPAL_GATEWAY_ID.
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/payment' )
|
|
||||||
.__internalSetActivePaymentMethod( PAYPAL_GATEWAY_ID, {} );
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
// fill the form if in continuation (for product or mini-cart buttons)
|
|
||||||
if ( continuationFilled || ! config.scriptData.continuation?.order ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const paypalAddresses = paypalOrderToWcAddresses(
|
|
||||||
config.scriptData.continuation.order
|
|
||||||
);
|
|
||||||
const wcAddresses = wp.data
|
|
||||||
.select( 'wc/store/cart' )
|
|
||||||
.getCustomerData();
|
|
||||||
const addresses = mergeWcAddress( wcAddresses, paypalAddresses );
|
|
||||||
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setBillingAddress( addresses.billingAddress );
|
|
||||||
|
|
||||||
if ( shippingData.needsShipping ) {
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setShippingAddress( addresses.shippingAddress );
|
|
||||||
}
|
|
||||||
} catch ( err ) {
|
|
||||||
// sometimes the PayPal address is missing, skip in this case.
|
|
||||||
console.log( err );
|
|
||||||
}
|
|
||||||
|
|
||||||
// this useEffect should run only once, but adding this in case of some kind of full re-rendering
|
|
||||||
setContinuationFilled( true );
|
|
||||||
}, [ shippingData, continuationFilled ] );
|
|
||||||
|
|
||||||
const createOrder = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const requestBody = {
|
|
||||||
nonce: config.scriptData.ajax.create_order.nonce,
|
|
||||||
bn_code: '',
|
|
||||||
context: config.scriptData.context,
|
|
||||||
payment_method: 'ppcp-gateway',
|
|
||||||
funding_source: window.ppcpFundingSource ?? 'paypal',
|
|
||||||
createaccount: false,
|
|
||||||
...( data?.paymentSource && {
|
|
||||||
payment_source: data.paymentSource,
|
|
||||||
} ),
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
config.scriptData.ajax.create_order.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( requestBody ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
if ( json.data?.details?.length > 0 ) {
|
|
||||||
throw new Error(
|
|
||||||
json.data.details
|
|
||||||
.map( ( d ) => `${ d.issue } ${ d.description }` )
|
|
||||||
.join( '<br/>' )
|
|
||||||
);
|
|
||||||
} else if ( json.data?.message ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error( config.scriptData.labels.error.generic );
|
|
||||||
}
|
|
||||||
return json.data.id;
|
|
||||||
} catch ( err ) {
|
|
||||||
console.error( err );
|
|
||||||
|
|
||||||
onError( err.message );
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSubscription = async ( data, actions ) => {
|
|
||||||
let planId = config.scriptData.subscription_plan_id;
|
|
||||||
if (
|
|
||||||
config.scriptData
|
|
||||||
.variable_paypal_subscription_variation_from_cart !== ''
|
|
||||||
) {
|
|
||||||
planId =
|
|
||||||
config.scriptData
|
|
||||||
.variable_paypal_subscription_variation_from_cart;
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions.subscription.create( {
|
|
||||||
plan_id: planId,
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleApproveSubscription = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const subscription = await actions.subscription.get();
|
|
||||||
|
|
||||||
if ( subscription ) {
|
|
||||||
const addresses =
|
|
||||||
paypalSubscriptionToWcAddresses( subscription );
|
|
||||||
|
|
||||||
const promises = [
|
|
||||||
// save address on server
|
|
||||||
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
|
||||||
billing_address: addresses.billingAddress,
|
|
||||||
shipping_address: addresses.shippingAddress,
|
|
||||||
} ),
|
|
||||||
];
|
|
||||||
if ( shouldHandleShippingInPayPal() ) {
|
|
||||||
// set address in UI
|
|
||||||
promises.push(
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setBillingAddress( addresses.billingAddress )
|
|
||||||
);
|
|
||||||
if ( shippingData.needsShipping ) {
|
|
||||||
promises.push(
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setShippingAddress( addresses.shippingAddress )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all( promises );
|
|
||||||
}
|
|
||||||
|
|
||||||
setPaypalOrder( subscription );
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
config.scriptData.ajax.approve_subscription.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: config.scriptData.ajax.approve_subscription
|
|
||||||
.nonce,
|
|
||||||
order_id: data.orderID,
|
|
||||||
subscription_id: data.subscriptionID,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
if (
|
|
||||||
typeof actions !== 'undefined' &&
|
|
||||||
typeof actions.restart !== 'undefined'
|
|
||||||
) {
|
|
||||||
return actions.restart();
|
|
||||||
}
|
|
||||||
if ( json.data?.message ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error( config.scriptData.labels.error.generic );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! shouldskipFinalConfirmation() ) {
|
|
||||||
location.href = getCheckoutRedirectUrl();
|
|
||||||
} else {
|
|
||||||
setGotoContinuationOnError( true );
|
|
||||||
enforcePaymentMethodForCart();
|
|
||||||
onSubmit();
|
|
||||||
}
|
|
||||||
} catch ( err ) {
|
|
||||||
console.error( err );
|
|
||||||
|
|
||||||
onError( err.message );
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCheckoutRedirectUrl = () => {
|
|
||||||
const checkoutUrl = new URL( config.scriptData.redirect );
|
|
||||||
// sometimes some browsers may load some kind of cached version of the page,
|
|
||||||
// so adding a parameter to avoid that
|
|
||||||
checkoutUrl.searchParams.append(
|
|
||||||
'ppcp-continuation-redirect',
|
|
||||||
new Date().getTime().toString()
|
|
||||||
);
|
|
||||||
return checkoutUrl.toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleApprove = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const order = await actions.order.get();
|
|
||||||
|
|
||||||
if ( order ) {
|
|
||||||
const addresses = paypalOrderToWcAddresses( order );
|
|
||||||
|
|
||||||
const promises = [
|
|
||||||
// save address on server
|
|
||||||
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
|
||||||
billing_address: addresses.billingAddress,
|
|
||||||
shipping_address: addresses.shippingAddress,
|
|
||||||
} ),
|
|
||||||
];
|
|
||||||
if ( shouldHandleShippingInPayPal() ) {
|
|
||||||
// set address in UI
|
|
||||||
promises.push(
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setBillingAddress( addresses.billingAddress )
|
|
||||||
);
|
|
||||||
if ( shippingData.needsShipping ) {
|
|
||||||
promises.push(
|
|
||||||
wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.setShippingAddress( addresses.shippingAddress )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all( promises );
|
|
||||||
}
|
|
||||||
|
|
||||||
setPaypalOrder( order );
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
config.scriptData.ajax.approve_order.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: config.scriptData.ajax.approve_order.nonce,
|
|
||||||
order_id: data.orderID,
|
|
||||||
funding_source: window.ppcpFundingSource ?? 'paypal',
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
if (
|
|
||||||
typeof actions !== 'undefined' &&
|
|
||||||
typeof actions.restart !== 'undefined'
|
|
||||||
) {
|
|
||||||
return actions.restart();
|
|
||||||
}
|
|
||||||
if ( json.data?.message ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error( config.scriptData.labels.error.generic );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! shouldskipFinalConfirmation() ) {
|
|
||||||
location.href = getCheckoutRedirectUrl();
|
|
||||||
} else {
|
|
||||||
setGotoContinuationOnError( true );
|
|
||||||
enforcePaymentMethodForCart();
|
|
||||||
onSubmit();
|
|
||||||
}
|
|
||||||
} catch ( err ) {
|
|
||||||
console.error( err );
|
|
||||||
|
|
||||||
onError( err.message );
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
const unsubscribe = onCheckoutValidation( () => {
|
|
||||||
if ( config.scriptData.continuation ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
gotoContinuationOnError &&
|
|
||||||
wp.data.select( 'wc/store/validation' ).hasValidationErrors()
|
|
||||||
) {
|
|
||||||
location.href = getCheckoutRedirectUrl();
|
|
||||||
return { type: responseTypes.ERROR };
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} );
|
|
||||||
return unsubscribe;
|
|
||||||
}, [ onCheckoutValidation, gotoContinuationOnError ] );
|
|
||||||
|
|
||||||
const handleClick = ( data, actions ) => {
|
|
||||||
if ( isEditing ) {
|
|
||||||
return actions.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ppcpFundingSource = data.fundingSource;
|
|
||||||
|
|
||||||
onClick();
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldHandleShippingInPayPal = () => {
|
|
||||||
return shouldskipFinalConfirmation() && config.needShipping;
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldskipFinalConfirmation = () => {
|
|
||||||
if ( config.finalReviewEnabled ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
window.ppcpFundingSource !== 'venmo' ||
|
|
||||||
! config.scriptData.vaultingEnabled
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handleShippingOptionsChange = null;
|
|
||||||
let handleShippingAddressChange = null;
|
|
||||||
let handleSubscriptionShippingOptionsChange = null;
|
|
||||||
let handleSubscriptionShippingAddressChange = null;
|
|
||||||
|
|
||||||
if ( shippingData.needsShipping && shouldHandleShippingInPayPal() ) {
|
|
||||||
handleShippingOptionsChange = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const shippingOptionId = data.selectedShippingOption?.id;
|
|
||||||
if ( shippingOptionId ) {
|
|
||||||
await wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.selectShippingRate( shippingOptionId );
|
|
||||||
await shippingData.setSelectedRates( shippingOptionId );
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: config.ajax.update_shipping.nonce,
|
|
||||||
order_id: data.orderID,
|
|
||||||
} ),
|
|
||||||
} );
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
} catch ( e ) {
|
|
||||||
console.error( e );
|
|
||||||
|
|
||||||
actions.reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleShippingAddressChange = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const address = paypalAddressToWc(
|
|
||||||
convertKeysToSnakeCase( data.shippingAddress )
|
|
||||||
);
|
|
||||||
|
|
||||||
await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
|
||||||
shipping_address: address,
|
|
||||||
} );
|
|
||||||
|
|
||||||
await shippingData.setShippingAddress( address );
|
|
||||||
|
|
||||||
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: config.ajax.update_shipping.nonce,
|
|
||||||
order_id: data.orderID,
|
|
||||||
} ),
|
|
||||||
} );
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
} catch ( e ) {
|
|
||||||
console.error( e );
|
|
||||||
|
|
||||||
actions.reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubscriptionShippingOptionsChange = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const shippingOptionId = data.selectedShippingOption?.id;
|
|
||||||
if ( shippingOptionId ) {
|
|
||||||
await wp.data
|
|
||||||
.dispatch( 'wc/store/cart' )
|
|
||||||
.selectShippingRate( shippingOptionId );
|
|
||||||
await shippingData.setSelectedRates( shippingOptionId );
|
|
||||||
}
|
|
||||||
} catch ( e ) {
|
|
||||||
console.error( e );
|
|
||||||
|
|
||||||
actions.reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubscriptionShippingAddressChange = async ( data, actions ) => {
|
|
||||||
try {
|
|
||||||
const address = paypalAddressToWc(
|
|
||||||
convertKeysToSnakeCase( data.shippingAddress )
|
|
||||||
);
|
|
||||||
|
|
||||||
await wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
|
||||||
shipping_address: address,
|
|
||||||
} );
|
|
||||||
|
|
||||||
await shippingData.setShippingAddress( address );
|
|
||||||
|
|
||||||
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: config.ajax.update_shipping.nonce,
|
|
||||||
order_id: data.orderID,
|
|
||||||
} ),
|
|
||||||
} );
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if ( ! json.success ) {
|
|
||||||
throw new Error( json.data.message );
|
|
||||||
}
|
|
||||||
} catch ( e ) {
|
|
||||||
console.error( e );
|
|
||||||
|
|
||||||
actions.reject();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
if ( activePaymentMethod !== methodId ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsubscribeProcessing = onPaymentSetup( () => {
|
|
||||||
if ( config.scriptData.continuation ) {
|
|
||||||
return {
|
|
||||||
type: responseTypes.SUCCESS,
|
|
||||||
meta: {
|
|
||||||
paymentMethodData: {
|
|
||||||
paypal_order_id:
|
|
||||||
config.scriptData.continuation.order_id,
|
|
||||||
funding_source:
|
|
||||||
window.ppcpFundingSource ?? 'paypal',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const addresses = paypalOrderToWcAddresses( paypalOrder );
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: responseTypes.SUCCESS,
|
|
||||||
meta: {
|
|
||||||
paymentMethodData: {
|
|
||||||
paypal_order_id: paypalOrder.id,
|
|
||||||
funding_source: window.ppcpFundingSource ?? 'paypal',
|
|
||||||
},
|
|
||||||
...addresses,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} );
|
|
||||||
return () => {
|
|
||||||
unsubscribeProcessing();
|
|
||||||
};
|
|
||||||
}, [ onPaymentSetup, paypalOrder, activePaymentMethod ] );
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
if ( activePaymentMethod !== methodId ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const unsubscribe = onCheckoutFail( ( { processingResponse } ) => {
|
|
||||||
console.error( processingResponse );
|
|
||||||
if ( onClose ) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
if ( config.scriptData.continuation ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ( shouldskipFinalConfirmation() ) {
|
|
||||||
location.href = getCheckoutRedirectUrl();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} );
|
|
||||||
return unsubscribe;
|
|
||||||
}, [ onCheckoutFail, onClose, activePaymentMethod ] );
|
|
||||||
|
|
||||||
if ( config.scriptData.continuation ) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={ {
|
|
||||||
__html: config.scriptData.continuation.cancel.html,
|
|
||||||
} }
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! registeredContext ) {
|
|
||||||
buttonModuleWatcher.registerContextBootstrap(
|
|
||||||
config.scriptData.context,
|
|
||||||
{
|
|
||||||
createOrder: () => {
|
|
||||||
return createOrder();
|
|
||||||
},
|
|
||||||
onApprove: ( data, actions ) => {
|
|
||||||
return handleApprove( data, actions );
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
registeredContext = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = normalizeStyleForFundingSource(
|
|
||||||
config.scriptData.button.style,
|
|
||||||
fundingSource
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( typeof buttonAttributes !== 'undefined' ) {
|
|
||||||
style.height = buttonAttributes?.height
|
|
||||||
? Number( buttonAttributes.height )
|
|
||||||
: style.height;
|
|
||||||
style.borderRadius = buttonAttributes?.borderRadius
|
|
||||||
? Number( buttonAttributes.borderRadius )
|
|
||||||
: style.borderRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! paypalScriptLoaded ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PayPalButton = ppcpBlocksPaypalExpressButtons.Buttons.driver(
|
|
||||||
'react',
|
|
||||||
{ React, ReactDOM }
|
|
||||||
);
|
|
||||||
|
|
||||||
const getOnShippingOptionsChange = ( fundingSource ) => {
|
|
||||||
if ( fundingSource === 'venmo' ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( data, actions ) => {
|
|
||||||
shouldHandleShippingInPayPal()
|
|
||||||
? handleShippingOptionsChange( data, actions )
|
|
||||||
: null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOnShippingAddressChange = ( fundingSource ) => {
|
|
||||||
if ( fundingSource === 'venmo' ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( data, actions ) => {
|
|
||||||
const shippingAddressChange = shouldHandleShippingInPayPal()
|
|
||||||
? handleShippingAddressChange( data, actions )
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return shippingAddressChange;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( isPayPalSubscription( config.scriptData ) ) {
|
|
||||||
return (
|
|
||||||
<PayPalButton
|
|
||||||
fundingSource={ fundingSource }
|
|
||||||
style={ style }
|
|
||||||
onClick={ handleClick }
|
|
||||||
onCancel={ onClose }
|
|
||||||
onError={ onClose }
|
|
||||||
createSubscription={ createSubscription }
|
|
||||||
onApprove={ handleApproveSubscription }
|
|
||||||
onShippingOptionsChange={ getOnShippingOptionsChange(
|
|
||||||
fundingSource
|
|
||||||
) }
|
|
||||||
onShippingAddressChange={ getOnShippingAddressChange(
|
|
||||||
fundingSource
|
|
||||||
) }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PayPalButton
|
|
||||||
fundingSource={ fundingSource }
|
|
||||||
style={ style }
|
|
||||||
onClick={ handleClick }
|
|
||||||
onCancel={ onClose }
|
|
||||||
onError={ onClose }
|
|
||||||
createOrder={ createOrder }
|
|
||||||
onApprove={ handleApprove }
|
|
||||||
onShippingOptionsChange={ getOnShippingOptionsChange(
|
|
||||||
fundingSource
|
|
||||||
) }
|
|
||||||
onShippingAddressChange={ getOnShippingAddressChange(
|
|
||||||
fundingSource
|
|
||||||
) }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BlockEditorPayPalComponent = ( { fundingSource, buttonAttributes } ) => {
|
|
||||||
const urlParams = useMemo(
|
|
||||||
() => ( {
|
|
||||||
clientId: 'test',
|
|
||||||
...config.scriptData.url_params,
|
|
||||||
dataNamespace: 'ppcp-blocks-editor-paypal-buttons',
|
|
||||||
components: 'buttons',
|
|
||||||
} ),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const style = useMemo( () => {
|
|
||||||
const configStyle = normalizeStyleForFundingSource(
|
|
||||||
config.scriptData.button.style,
|
|
||||||
fundingSource
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( buttonAttributes ) {
|
|
||||||
return {
|
|
||||||
...configStyle,
|
|
||||||
height: buttonAttributes.height
|
|
||||||
? Number( buttonAttributes.height )
|
|
||||||
: configStyle.height,
|
|
||||||
borderRadius: buttonAttributes.borderRadius
|
|
||||||
? Number( buttonAttributes.borderRadius )
|
|
||||||
: configStyle.borderRadius,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return configStyle;
|
|
||||||
}, [ fundingSource, buttonAttributes ] );
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PayPalScriptProvider options={ urlParams }>
|
|
||||||
<PayPalButtons
|
|
||||||
className={ `ppc-button-container-${ fundingSource }` }
|
|
||||||
fundingSource={ fundingSource }
|
|
||||||
style={ style }
|
|
||||||
forceReRender={ [ buttonAttributes || {} ] }
|
|
||||||
onClick={ () => false }
|
|
||||||
/>
|
|
||||||
</PayPalScriptProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const features = [ 'products' ];
|
const features = [ 'products' ];
|
||||||
let block_enabled = true;
|
let blockEnabled = true;
|
||||||
|
|
||||||
if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
||||||
// Don't show buttons on block cart page if using vault v2 and user is not logged in
|
// Don't show buttons on block cart page if using vault v2 and user is not logged in
|
||||||
|
@ -754,7 +30,17 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
||||||
! isPayPalSubscription( config.scriptData ) && // using vaulting
|
! isPayPalSubscription( config.scriptData ) && // using vaulting
|
||||||
! config.scriptData?.save_payment_methods?.id_token // not vault v3
|
! config.scriptData?.save_payment_methods?.id_token // not vault v3
|
||||||
) {
|
) {
|
||||||
block_enabled = false;
|
blockEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show buttons on block cart page if user is not logged in and cart contains free trial product
|
||||||
|
if (
|
||||||
|
! config.scriptData.user.is_logged &&
|
||||||
|
config.scriptData.context === 'cart-block' &&
|
||||||
|
cartHasSubscriptionProducts( config.scriptData ) &&
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
) {
|
||||||
|
blockEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't render if vaulting disabled and is in vault subscription mode
|
// Don't render if vaulting disabled and is in vault subscription mode
|
||||||
|
@ -762,7 +48,7 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
||||||
! isPayPalSubscription( config.scriptData ) &&
|
! isPayPalSubscription( config.scriptData ) &&
|
||||||
! config.scriptData.can_save_vault_token
|
! config.scriptData.can_save_vault_token
|
||||||
) {
|
) {
|
||||||
block_enabled = false;
|
blockEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
|
// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
|
||||||
|
@ -770,13 +56,21 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
||||||
isPayPalSubscription( config.scriptData ) &&
|
isPayPalSubscription( config.scriptData ) &&
|
||||||
! config.scriptData.subscription_product_allowed
|
! config.scriptData.subscription_product_allowed
|
||||||
) {
|
) {
|
||||||
block_enabled = false;
|
blockEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show buttons if cart contains free trial product and the stroe is not eligible for saving payment methods.
|
||||||
|
if (
|
||||||
|
! config.scriptData.vault_v3_enabled &&
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
) {
|
||||||
|
blockEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
features.push( 'subscriptions' );
|
features.push( 'subscriptions' );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( block_enabled ) {
|
if ( blockEnabled ) {
|
||||||
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
|
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
|
||||||
let descriptionElement = (
|
let descriptionElement = (
|
||||||
<div
|
<div
|
||||||
|
@ -802,21 +96,6 @@ if ( block_enabled ) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaypalLabel = ( { components, config } ) => {
|
|
||||||
const { PaymentMethodIcons } = components;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
dangerouslySetInnerHTML={ {
|
|
||||||
__html: config.title,
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
<PaymentMethodIcons icons={ config.icon } align="right" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
registerPaymentMethod( {
|
registerPaymentMethod( {
|
||||||
name: config.id,
|
name: config.id,
|
||||||
label: <PaypalLabel config={ config } />,
|
label: <PaypalLabel config={ config } />,
|
||||||
|
@ -837,8 +116,13 @@ if ( block_enabled ) {
|
||||||
registerPaymentMethod( {
|
registerPaymentMethod( {
|
||||||
name: config.id,
|
name: config.id,
|
||||||
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
|
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
|
||||||
content: <PayPalComponent isEditing={ false } />,
|
content: <PayPalComponent config={ config } isEditing={ false } />,
|
||||||
edit: <BlockEditorPayPalComponent fundingSource={ 'paypal' } />,
|
edit: (
|
||||||
|
<BlockEditorPayPalComponent
|
||||||
|
config={ config }
|
||||||
|
fundingSource={ 'paypal' }
|
||||||
|
/>
|
||||||
|
),
|
||||||
ariaLabel: config.title,
|
ariaLabel: config.title,
|
||||||
canMakePayment: () => {
|
canMakePayment: () => {
|
||||||
return true;
|
return true;
|
||||||
|
@ -848,10 +132,11 @@ if ( block_enabled ) {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
} else if ( config.smartButtonsEnabled ) {
|
} else if ( config.smartButtonsEnabled ) {
|
||||||
for ( const fundingSource of [
|
const fundingSources = config.scriptData.is_free_trial_cart
|
||||||
'paypal',
|
? [ 'paypal' ]
|
||||||
...config.enabledFundingSources,
|
: [ 'paypal', ...config.enabledFundingSources ];
|
||||||
] ) {
|
|
||||||
|
for ( const fundingSource of fundingSources ) {
|
||||||
registerExpressPaymentMethod( {
|
registerExpressPaymentMethod( {
|
||||||
name: `${ config.id }-${ fundingSource }`,
|
name: `${ config.id }-${ fundingSource }`,
|
||||||
title: 'PayPal',
|
title: 'PayPal',
|
||||||
|
@ -866,12 +151,14 @@ if ( block_enabled ) {
|
||||||
),
|
),
|
||||||
content: (
|
content: (
|
||||||
<PayPalComponent
|
<PayPalComponent
|
||||||
|
config={ config }
|
||||||
isEditing={ false }
|
isEditing={ false }
|
||||||
fundingSource={ fundingSource }
|
fundingSource={ fundingSource }
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
edit: (
|
edit: (
|
||||||
<BlockEditorPayPalComponent
|
<BlockEditorPayPalComponent
|
||||||
|
config={ config }
|
||||||
fundingSource={ fundingSource }
|
fundingSource={ fundingSource }
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
316
modules/ppcp-blocks/resources/js/paypal-config.js
Normal file
316
modules/ppcp-blocks/resources/js/paypal-config.js
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
import {
|
||||||
|
paypalOrderToWcAddresses,
|
||||||
|
paypalSubscriptionToWcAddresses,
|
||||||
|
} from './Helper/Address';
|
||||||
|
|
||||||
|
export const createOrder = async ( data, config, onError, onClose ) => {
|
||||||
|
try {
|
||||||
|
const requestBody = {
|
||||||
|
nonce: config.scriptData.ajax.create_order.nonce,
|
||||||
|
bn_code: '',
|
||||||
|
context: config.scriptData.context,
|
||||||
|
payment_method: 'ppcp-gateway',
|
||||||
|
funding_source: window.ppcpFundingSource ?? 'paypal',
|
||||||
|
createaccount: false,
|
||||||
|
...( data?.paymentSource && {
|
||||||
|
payment_source: data.paymentSource,
|
||||||
|
} ),
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch( config.scriptData.ajax.create_order.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( requestBody ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
if ( json.data?.details?.length > 0 ) {
|
||||||
|
throw new Error(
|
||||||
|
json.data.details
|
||||||
|
.map( ( d ) => `${ d.issue } ${ d.description }` )
|
||||||
|
.join( '<br/>' )
|
||||||
|
);
|
||||||
|
} else if ( json.data?.message ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error( config.scriptData.labels.error.generic );
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.data.id;
|
||||||
|
} catch ( err ) {
|
||||||
|
console.error( err );
|
||||||
|
|
||||||
|
onError( err.message );
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleApprove = async (
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
config,
|
||||||
|
shouldHandleShippingInPayPal,
|
||||||
|
shippingData,
|
||||||
|
setPaypalOrder,
|
||||||
|
shouldskipFinalConfirmation,
|
||||||
|
getCheckoutRedirectUrl,
|
||||||
|
setGotoContinuationOnError,
|
||||||
|
enforcePaymentMethodForCart,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
onClose
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const order = await actions.order.get();
|
||||||
|
|
||||||
|
if ( order ) {
|
||||||
|
const addresses = paypalOrderToWcAddresses( order );
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
// save address on server
|
||||||
|
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
||||||
|
billing_address: addresses.billingAddress,
|
||||||
|
shipping_address: addresses.shippingAddress,
|
||||||
|
} ),
|
||||||
|
];
|
||||||
|
if ( shouldHandleShippingInPayPal() ) {
|
||||||
|
// set address in UI
|
||||||
|
promises.push(
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setBillingAddress( addresses.billingAddress )
|
||||||
|
);
|
||||||
|
if ( shippingData.needsShipping ) {
|
||||||
|
promises.push(
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setShippingAddress( addresses.shippingAddress )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all( promises );
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaypalOrder( order );
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
config.scriptData.ajax.approve_order.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.scriptData.ajax.approve_order.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
funding_source: window.ppcpFundingSource ?? 'paypal',
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
if (
|
||||||
|
typeof actions !== 'undefined' &&
|
||||||
|
typeof actions.restart !== 'undefined'
|
||||||
|
) {
|
||||||
|
return actions.restart();
|
||||||
|
}
|
||||||
|
if ( json.data?.message ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error( config.scriptData.labels.error.generic );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! shouldskipFinalConfirmation() ) {
|
||||||
|
location.href = getCheckoutRedirectUrl();
|
||||||
|
} else {
|
||||||
|
setGotoContinuationOnError( true );
|
||||||
|
enforcePaymentMethodForCart();
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
} catch ( err ) {
|
||||||
|
console.error( err );
|
||||||
|
|
||||||
|
onError( err.message );
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSubscription = async ( data, actions, config ) => {
|
||||||
|
let planId = config.scriptData.subscription_plan_id;
|
||||||
|
if (
|
||||||
|
config.scriptData.variable_paypal_subscription_variation_from_cart !==
|
||||||
|
''
|
||||||
|
) {
|
||||||
|
planId =
|
||||||
|
config.scriptData.variable_paypal_subscription_variation_from_cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions.subscription.create( {
|
||||||
|
plan_id: planId,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleApproveSubscription = async (
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
config,
|
||||||
|
shouldHandleShippingInPayPal,
|
||||||
|
shippingData,
|
||||||
|
setPaypalOrder,
|
||||||
|
shouldskipFinalConfirmation,
|
||||||
|
getCheckoutRedirectUrl,
|
||||||
|
setGotoContinuationOnError,
|
||||||
|
enforcePaymentMethodForCart,
|
||||||
|
onSubmit,
|
||||||
|
onError,
|
||||||
|
onClose
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const subscription = await actions.subscription.get();
|
||||||
|
|
||||||
|
if ( subscription ) {
|
||||||
|
const addresses = paypalSubscriptionToWcAddresses( subscription );
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
// save address on server
|
||||||
|
wp.data.dispatch( 'wc/store/cart' ).updateCustomerData( {
|
||||||
|
billing_address: addresses.billingAddress,
|
||||||
|
shipping_address: addresses.shippingAddress,
|
||||||
|
} ),
|
||||||
|
];
|
||||||
|
if ( shouldHandleShippingInPayPal() ) {
|
||||||
|
// set address in UI
|
||||||
|
promises.push(
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setBillingAddress( addresses.billingAddress )
|
||||||
|
);
|
||||||
|
if ( shippingData.needsShipping ) {
|
||||||
|
promises.push(
|
||||||
|
wp.data
|
||||||
|
.dispatch( 'wc/store/cart' )
|
||||||
|
.setShippingAddress( addresses.shippingAddress )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all( promises );
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaypalOrder( subscription );
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
config.scriptData.ajax.approve_subscription.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.scriptData.ajax.approve_subscription.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
subscription_id: data.subscriptionID,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
if (
|
||||||
|
typeof actions !== 'undefined' &&
|
||||||
|
typeof actions.restart !== 'undefined'
|
||||||
|
) {
|
||||||
|
return actions.restart();
|
||||||
|
}
|
||||||
|
if ( json.data?.message ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error( config.scriptData.labels.error.generic );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! shouldskipFinalConfirmation() ) {
|
||||||
|
location.href = getCheckoutRedirectUrl();
|
||||||
|
} else {
|
||||||
|
setGotoContinuationOnError( true );
|
||||||
|
enforcePaymentMethodForCart();
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
} catch ( err ) {
|
||||||
|
console.error( err );
|
||||||
|
|
||||||
|
onError( err.message );
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createVaultSetupToken = async ( config ) => {
|
||||||
|
return fetch( config.scriptData.ajax.create_setup_token.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.scriptData.ajax.create_setup_token.nonce,
|
||||||
|
payment_method: 'ppcp-gateway',
|
||||||
|
} ),
|
||||||
|
} )
|
||||||
|
.then( ( response ) => response.json() )
|
||||||
|
.then( ( result ) => {
|
||||||
|
return result.data.id;
|
||||||
|
} )
|
||||||
|
.catch( ( err ) => {
|
||||||
|
console.error( err );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onApproveSavePayment = async (
|
||||||
|
vaultSetupToken,
|
||||||
|
config,
|
||||||
|
onSubmit
|
||||||
|
) => {
|
||||||
|
let endpoint =
|
||||||
|
config.scriptData.ajax.create_payment_token_for_guest.endpoint;
|
||||||
|
let bodyContent = {
|
||||||
|
nonce: config.scriptData.ajax.create_payment_token_for_guest.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( config.scriptData.user.is_logged_in ) {
|
||||||
|
endpoint = config.scriptData.ajax.create_payment_token.endpoint;
|
||||||
|
|
||||||
|
bodyContent = {
|
||||||
|
nonce: config.scriptData.ajax.create_payment_token.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
is_free_trial_cart: config.scriptData.is_free_trial_cart,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch( endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( bodyContent ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.success === true ) {
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error( result );
|
||||||
|
};
|
|
@ -97,11 +97,16 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
|
||||||
wp_register_script(
|
wp_register_script(
|
||||||
'ppcp-advanced-card-checkout-block',
|
'ppcp-advanced-card-checkout-block',
|
||||||
trailingslashit( $this->module_url ) . 'assets/js/advanced-card-checkout-block.js',
|
trailingslashit( $this->module_url ) . 'assets/js/advanced-card-checkout-block.js',
|
||||||
array(),
|
array( 'wp-i18n' ),
|
||||||
$this->version,
|
$this->version,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
wp_set_script_translations(
|
||||||
|
'ppcp-advanced-card-checkout-block',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
|
||||||
return array( 'ppcp-advanced-card-checkout-block' );
|
return array( 'ppcp-advanced-card-checkout-block' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1910,7 +1910,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
|
||||||
|
|
||||||
$in_stock = $product->is_in_stock();
|
$in_stock = $product->is_in_stock();
|
||||||
|
|
||||||
if ( $product->is_type( 'variable' ) ) {
|
if ( ! $in_stock && $product->is_type( 'variable' ) ) {
|
||||||
/**
|
/**
|
||||||
* The method is defined in WC_Product_Variable class.
|
* The method is defined in WC_Product_Variable class.
|
||||||
*
|
*
|
||||||
|
|
|
@ -345,6 +345,10 @@ window.ppcp_onboarding_productionCallback = function ( ...args ) {
|
||||||
|
|
||||||
const sandboxSwitchElement = document.querySelector( '#ppcp-sandbox_on' );
|
const sandboxSwitchElement = document.querySelector( '#ppcp-sandbox_on' );
|
||||||
|
|
||||||
|
sandboxSwitchElement?.addEventListener( 'click', () => {
|
||||||
|
document.querySelector( '.woocommerce-save-button' )?.removeAttribute( 'disabled' );
|
||||||
|
});
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
const selectors = sandboxSwitchElement.checked
|
const selectors = sandboxSwitchElement.checked
|
||||||
? sandboxCredentialElementsSelectors
|
? sandboxCredentialElementsSelectors
|
||||||
|
|
|
@ -96,7 +96,7 @@ class CreatePaymentToken implements EndpointInterface {
|
||||||
|
|
||||||
$customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
$customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
||||||
|
|
||||||
$result = $this->payment_method_tokens_endpoint->create_payment_token( $payment_source, $customer_id );
|
$result = $this->payment_method_tokens_endpoint->create_payment_token( $payment_source, (string) $customer_id );
|
||||||
|
|
||||||
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
|
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
|
||||||
$current_user_id = get_current_user_id();
|
$current_user_id = get_current_user_id();
|
||||||
|
|
|
@ -105,7 +105,7 @@ class CreateSetupToken implements EndpointInterface {
|
||||||
|
|
||||||
$customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
$customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
||||||
|
|
||||||
$result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source, $customer_id );
|
$result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source, (string) $customer_id );
|
||||||
|
|
||||||
wp_send_json_success( $result );
|
wp_send_json_success( $result );
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -66,396 +66,382 @@ class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, Execut
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_gateway_migrate_on_update',
|
'woocommerce_paypal_payments_gateway_migrate_on_update',
|
||||||
function() use ( $c ) {
|
function() use ( $c ) {
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
||||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||||
|
|
||||||
$reference_transaction_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
$reference_transaction_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
||||||
if ( $reference_transaction_enabled !== true ) {
|
if ( $reference_transaction_enabled !== true ) {
|
||||||
$c->get( 'wcgateway.settings' )->set( 'vault_enabled', false );
|
$settings->set( 'vault_enabled', false );
|
||||||
$c->get( 'wcgateway.settings' )->persist();
|
$settings->persist();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
add_filter(
|
|
||||||
'woocommerce_paypal_payments_localized_script_data',
|
|
||||||
function( array $localized_script_data ) use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $localized_script_data;
|
|
||||||
}
|
|
||||||
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
|
|
||||||
assert( $subscriptions_helper instanceof SubscriptionHelper );
|
|
||||||
if ( ! is_user_logged_in() && ! $subscriptions_helper->cart_contains_subscription() ) {
|
|
||||||
return $localized_script_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$api = $c->get( 'api.user-id-token' );
|
|
||||||
assert( $api instanceof UserIdToken );
|
|
||||||
|
|
||||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
|
||||||
assert( $logger instanceof LoggerInterface );
|
|
||||||
|
|
||||||
return $this->add_id_token_to_script_data( $api, $logger, $localized_script_data );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Adds attributes needed to save payment method.
|
|
||||||
add_filter(
|
|
||||||
'ppcp_create_order_request_body_data',
|
|
||||||
function( array $data, string $payment_method, array $request_data ) use ( $c ): array {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
if ( $payment_method === CreditCardGateway::ID ) {
|
|
||||||
$save_payment_method = $request_data['save_payment_method'] ?? false;
|
|
||||||
if ( $save_payment_method ) {
|
|
||||||
$data['payment_source'] = array(
|
|
||||||
'card' => array(
|
|
||||||
'attributes' => array(
|
|
||||||
'vault' => array(
|
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
|
||||||
if ( ! $target_customer_id ) {
|
|
||||||
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $target_customer_id ) {
|
|
||||||
$data['payment_source']['card']['attributes']['customer'] = array(
|
|
||||||
'id' => $target_customer_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $payment_method === PayPalGateway::ID ) {
|
|
||||||
$funding_source = $request_data['funding_source'] ?? null;
|
|
||||||
|
|
||||||
if ( $funding_source && $funding_source === 'venmo' ) {
|
|
||||||
$data['payment_source'] = array(
|
|
||||||
'venmo' => array(
|
|
||||||
'attributes' => array(
|
|
||||||
'vault' => array(
|
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
|
||||||
'usage_type' => 'MERCHANT',
|
|
||||||
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} elseif ( $funding_source && $funding_source === 'apple_pay' ) {
|
|
||||||
$data['payment_source'] = array(
|
|
||||||
'apple_pay' => array(
|
|
||||||
'stored_credential' => array(
|
|
||||||
'payment_initiator' => 'CUSTOMER',
|
|
||||||
'payment_type' => 'RECURRING',
|
|
||||||
),
|
|
||||||
'attributes' => array(
|
|
||||||
'vault' => array(
|
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$data['payment_source'] = array(
|
|
||||||
'paypal' => array(
|
|
||||||
'attributes' => array(
|
|
||||||
'vault' => array(
|
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
|
||||||
'usage_type' => 'MERCHANT',
|
|
||||||
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
|
||||||
'woocommerce_paypal_payments_after_order_processor',
|
|
||||||
function( WC_Order $wc_order, Order $order ) use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$payment_source = $order->payment_source();
|
|
||||||
assert( $payment_source instanceof PaymentSource );
|
|
||||||
|
|
||||||
$payment_vault_attributes = $payment_source->properties()->attributes->vault ?? null;
|
|
||||||
if ( $payment_vault_attributes ) {
|
|
||||||
$customer_id = $payment_vault_attributes->customer->id ?? '';
|
|
||||||
$token_id = $payment_vault_attributes->id ?? '';
|
|
||||||
if ( ! $customer_id || ! $token_id ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
|
|
||||||
|
|
||||||
$wc_payment_tokens = $c->get( 'vaulting.wc-payment-tokens' );
|
|
||||||
assert( $wc_payment_tokens instanceof WooCommercePaymentTokens );
|
|
||||||
|
|
||||||
if ( $wc_order->get_payment_method() === CreditCardGateway::ID ) {
|
|
||||||
$token = new \WC_Payment_Token_CC();
|
|
||||||
$token->set_token( $token_id );
|
|
||||||
$token->set_user_id( $wc_order->get_customer_id() );
|
|
||||||
$token->set_gateway_id( CreditCardGateway::ID );
|
|
||||||
|
|
||||||
$token->set_last4( $payment_source->properties()->last_digits ?? '' );
|
|
||||||
$expiry = explode( '-', $payment_source->properties()->expiry ?? '' );
|
|
||||||
$token->set_expiry_year( $expiry[0] ?? '' );
|
|
||||||
$token->set_expiry_month( $expiry[1] ?? '' );
|
|
||||||
$token->set_card_type( $payment_source->properties()->brand ?? '' );
|
|
||||||
|
|
||||||
$token->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
|
||||||
switch ( $payment_source->name() ) {
|
|
||||||
case 'venmo':
|
|
||||||
$wc_payment_tokens->create_payment_token_venmo(
|
|
||||||
$wc_order->get_customer_id(),
|
|
||||||
$token_id,
|
|
||||||
$payment_source->properties()->email_address ?? ''
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'apple_pay':
|
|
||||||
$wc_payment_tokens->create_payment_token_applepay(
|
|
||||||
$wc_order->get_customer_id(),
|
|
||||||
$token_id
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'paypal':
|
|
||||||
default:
|
|
||||||
$wc_payment_tokens->create_payment_token_paypal(
|
|
||||||
$wc_order->get_customer_id(),
|
|
||||||
$token_id,
|
|
||||||
$payment_source->properties()->email_address ?? ''
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
|
|
||||||
add_filter(
|
|
||||||
'woocommerce_paypal_payments_disable_add_payment_method',
|
|
||||||
function ( bool $value ) use ( $c ): bool {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_filter(
|
|
||||||
'woocommerce_paypal_payments_should_render_card_custom_fields',
|
|
||||||
function ( bool $value ) use ( $c ): bool {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
'wp_enqueue_scripts',
|
'after_setup_theme',
|
||||||
function() use ( $c ) {
|
function () use ( $c ) {
|
||||||
if ( ! is_user_logged_in() || ! ( $this->is_add_payment_method_page() || $this->is_subscription_change_payment_method_page() ) || ! self::vault_enabled( $c ) ) {
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
return;
|
if (
|
||||||
|
( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) )
|
||||||
|
&& ( ! $settings->has( 'vault_enabled_dcc' ) || ! $settings->get( 'vault_enabled_dcc' ) )
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$module_url = $c->get( 'save-payment-methods.module.url' );
|
add_filter(
|
||||||
wp_enqueue_script(
|
'woocommerce_paypal_payments_localized_script_data',
|
||||||
'ppcp-add-payment-method',
|
function ( array $localized_script_data ) use ( $c ) {
|
||||||
untrailingslashit( $module_url ) . '/assets/js/add-payment-method.js',
|
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
|
||||||
array( 'jquery' ),
|
assert( $subscriptions_helper instanceof SubscriptionHelper );
|
||||||
$c->get( 'ppcp.asset-version' ),
|
if ( ! is_user_logged_in() && ! $subscriptions_helper->cart_contains_subscription() ) {
|
||||||
true
|
return $localized_script_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = $c->get( 'api.user-id-token' );
|
||||||
|
assert( $api instanceof UserIdToken );
|
||||||
|
|
||||||
|
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
assert( $logger instanceof LoggerInterface );
|
||||||
|
|
||||||
|
return $this->add_id_token_to_script_data( $api, $logger, $localized_script_data );
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$api = $c->get( 'api.user-id-token' );
|
// Adds attributes needed to save payment method.
|
||||||
assert( $api instanceof UserIdToken );
|
add_filter(
|
||||||
|
'ppcp_create_order_request_body_data',
|
||||||
|
function ( array $data, string $payment_method, array $request_data ) use ( $c ): array {
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
if ( $payment_method === CreditCardGateway::ID ) {
|
||||||
|
if ( ! $settings->has( 'vault_enabled_dcc' ) || ! $settings->get( 'vault_enabled_dcc' ) ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
$save_payment_method = $request_data['save_payment_method'] ?? false;
|
||||||
$target_customer_id = '';
|
if ( $save_payment_method ) {
|
||||||
if ( is_user_logged_in() ) {
|
$data['payment_source'] = array(
|
||||||
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
'card' => array(
|
||||||
if ( ! $target_customer_id ) {
|
'attributes' => array(
|
||||||
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
|
'vault' => array(
|
||||||
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
||||||
|
if ( ! $target_customer_id ) {
|
||||||
|
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $target_customer_id ) {
|
||||||
|
$data['payment_source']['card']['attributes']['customer'] = array(
|
||||||
|
'id' => $target_customer_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $payment_method === PayPalGateway::ID ) {
|
||||||
|
if ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$funding_source = $request_data['funding_source'] ?? null;
|
||||||
|
|
||||||
|
if ( $funding_source && $funding_source === 'venmo' ) {
|
||||||
|
$data['payment_source'] = array(
|
||||||
|
'venmo' => array(
|
||||||
|
'attributes' => array(
|
||||||
|
'vault' => array(
|
||||||
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
|
'usage_type' => 'MERCHANT',
|
||||||
|
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} elseif ( $funding_source && $funding_source === 'apple_pay' ) {
|
||||||
|
$data['payment_source'] = array(
|
||||||
|
'apple_pay' => array(
|
||||||
|
'stored_credential' => array(
|
||||||
|
'payment_initiator' => 'CUSTOMER',
|
||||||
|
'payment_type' => 'RECURRING',
|
||||||
|
),
|
||||||
|
'attributes' => array(
|
||||||
|
'vault' => array(
|
||||||
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data['payment_source'] = array(
|
||||||
|
'paypal' => array(
|
||||||
|
'attributes' => array(
|
||||||
|
'vault' => array(
|
||||||
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
|
'usage_type' => 'MERCHANT',
|
||||||
|
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_after_order_processor',
|
||||||
|
function ( WC_Order $wc_order, Order $order ) use ( $c ) {
|
||||||
|
$payment_source = $order->payment_source();
|
||||||
|
assert( $payment_source instanceof PaymentSource );
|
||||||
|
|
||||||
|
$payment_vault_attributes = $payment_source->properties()->attributes->vault ?? null;
|
||||||
|
if ( $payment_vault_attributes ) {
|
||||||
|
$customer_id = $payment_vault_attributes->customer->id ?? '';
|
||||||
|
$token_id = $payment_vault_attributes->id ?? '';
|
||||||
|
if ( ! $customer_id || ! $token_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
|
||||||
|
|
||||||
|
$wc_payment_tokens = $c->get( 'vaulting.wc-payment-tokens' );
|
||||||
|
assert( $wc_payment_tokens instanceof WooCommercePaymentTokens );
|
||||||
|
|
||||||
|
if ( $wc_order->get_payment_method() === CreditCardGateway::ID ) {
|
||||||
|
$token = new \WC_Payment_Token_CC();
|
||||||
|
$token->set_token( $token_id );
|
||||||
|
$token->set_user_id( $wc_order->get_customer_id() );
|
||||||
|
$token->set_gateway_id( CreditCardGateway::ID );
|
||||||
|
|
||||||
|
$token->set_last4( $payment_source->properties()->last_digits ?? '' );
|
||||||
|
$expiry = explode( '-', $payment_source->properties()->expiry ?? '' );
|
||||||
|
$token->set_expiry_year( $expiry[0] ?? '' );
|
||||||
|
$token->set_expiry_month( $expiry[1] ?? '' );
|
||||||
|
$token->set_card_type( $payment_source->properties()->brand ?? '' );
|
||||||
|
|
||||||
|
$token->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
||||||
|
switch ( $payment_source->name() ) {
|
||||||
|
case 'venmo':
|
||||||
|
$wc_payment_tokens->create_payment_token_venmo(
|
||||||
|
$wc_order->get_customer_id(),
|
||||||
|
$token_id,
|
||||||
|
$payment_source->properties()->email_address ?? ''
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'apple_pay':
|
||||||
|
$wc_payment_tokens->create_payment_token_applepay(
|
||||||
|
$wc_order->get_customer_id(),
|
||||||
|
$token_id
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'paypal':
|
||||||
|
default:
|
||||||
|
$wc_payment_tokens->create_payment_token_paypal(
|
||||||
|
$wc_order->get_customer_id(),
|
||||||
|
$token_id,
|
||||||
|
$payment_source->properties()->email_address ?? ''
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
add_filter( 'woocommerce_paypal_payments_disable_add_payment_method', '__return_false' );
|
||||||
|
add_filter( 'woocommerce_paypal_payments_should_render_card_custom_fields', '__return_false' );
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'wp_enqueue_scripts',
|
||||||
|
function () use ( $c ) {
|
||||||
|
if ( ! is_user_logged_in() || ! ( $this->is_add_payment_method_page() || $this->is_subscription_change_payment_method_page() ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$module_url = $c->get( 'save-payment-methods.module.url' );
|
||||||
|
wp_enqueue_script(
|
||||||
|
'ppcp-add-payment-method',
|
||||||
|
untrailingslashit( $module_url ) . '/assets/js/add-payment-method.js',
|
||||||
|
array( 'jquery' ),
|
||||||
|
$c->get( 'ppcp.asset-version' ),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$api = $c->get( 'api.user-id-token' );
|
||||||
|
assert( $api instanceof UserIdToken );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$target_customer_id = '';
|
||||||
|
if ( is_user_logged_in() ) {
|
||||||
|
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
|
||||||
|
if ( ! $target_customer_id ) {
|
||||||
|
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_token = $api->id_token( $target_customer_id );
|
||||||
|
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
$verification_method =
|
||||||
|
$settings->has( '3d_secure_contingency' )
|
||||||
|
? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
|
||||||
|
: '';
|
||||||
|
|
||||||
|
$change_payment_method = wc_clean( wp_unslash( $_GET['change_payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
|
||||||
|
|
||||||
|
wp_localize_script(
|
||||||
|
'ppcp-add-payment-method',
|
||||||
|
'ppcp_add_payment_method',
|
||||||
|
array(
|
||||||
|
'client_id' => $c->get( 'button.client_id' ),
|
||||||
|
'merchant_id' => $c->get( 'api.merchant_id' ),
|
||||||
|
'id_token' => $id_token,
|
||||||
|
'payment_methods_page' => wc_get_account_endpoint_url( 'payment-methods' ),
|
||||||
|
'view_subscriptions_page' => wc_get_account_endpoint_url( 'view-subscription' ),
|
||||||
|
'is_subscription_change_payment_page' => $this->is_subscription_change_payment_method_page(),
|
||||||
|
'subscription_id_to_change_payment' => $this->is_subscription_change_payment_method_page() ? (int) $change_payment_method : 0,
|
||||||
|
'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ),
|
||||||
|
'verification_method' => $verification_method,
|
||||||
|
'ajax' => array(
|
||||||
|
'create_setup_token' => array(
|
||||||
|
'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ),
|
||||||
|
'nonce' => wp_create_nonce( CreateSetupToken::nonce() ),
|
||||||
|
),
|
||||||
|
'create_payment_token' => array(
|
||||||
|
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ),
|
||||||
|
'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ),
|
||||||
|
),
|
||||||
|
'subscription_change_payment_method' => array(
|
||||||
|
'endpoint' => \WC_AJAX::get_endpoint( SubscriptionChangePaymentMethod::ENDPOINT ),
|
||||||
|
'nonce' => wp_create_nonce( SubscriptionChangePaymentMethod::nonce() ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'labels' => array(
|
||||||
|
'error' => array(
|
||||||
|
'generic' => __(
|
||||||
|
'Something went wrong. Please try again or choose another payment source.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch ( RuntimeException $exception ) {
|
||||||
|
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
assert( $logger instanceof LoggerInterface );
|
||||||
|
|
||||||
|
$error = $exception->getMessage();
|
||||||
|
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||||
|
$error = $exception->get_details( $error );
|
||||||
|
}
|
||||||
|
|
||||||
|
$logger->error( $error );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$id_token = $api->id_token( $target_customer_id );
|
add_action(
|
||||||
|
'woocommerce_add_payment_method_form_bottom',
|
||||||
|
function () {
|
||||||
|
if ( ! is_user_logged_in() || ! is_add_payment_method_page() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$settings = $c->get( 'wcgateway.settings' );
|
echo '<div id="ppc-button-' . esc_attr( PayPalGateway::ID ) . '-save-payment-method"></div>';
|
||||||
assert( $settings instanceof Settings );
|
|
||||||
|
|
||||||
$verification_method =
|
|
||||||
$settings->has( '3d_secure_contingency' )
|
|
||||||
? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
|
|
||||||
: '';
|
|
||||||
|
|
||||||
$change_payment_method = wc_clean( wp_unslash( $_GET['change_payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
|
|
||||||
|
|
||||||
wp_localize_script(
|
|
||||||
'ppcp-add-payment-method',
|
|
||||||
'ppcp_add_payment_method',
|
|
||||||
array(
|
|
||||||
'client_id' => $c->get( 'button.client_id' ),
|
|
||||||
'merchant_id' => $c->get( 'api.merchant_id' ),
|
|
||||||
'id_token' => $id_token,
|
|
||||||
'payment_methods_page' => wc_get_account_endpoint_url( 'payment-methods' ),
|
|
||||||
'view_subscriptions_page' => wc_get_account_endpoint_url( 'view-subscription' ),
|
|
||||||
'is_subscription_change_payment_page' => $this->is_subscription_change_payment_method_page(),
|
|
||||||
'subscription_id_to_change_payment' => $this->is_subscription_change_payment_method_page() ? (int) $change_payment_method : 0,
|
|
||||||
'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ),
|
|
||||||
'verification_method' => $verification_method,
|
|
||||||
'ajax' => array(
|
|
||||||
'create_setup_token' => array(
|
|
||||||
'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ),
|
|
||||||
'nonce' => wp_create_nonce( CreateSetupToken::nonce() ),
|
|
||||||
),
|
|
||||||
'create_payment_token' => array(
|
|
||||||
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ),
|
|
||||||
'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ),
|
|
||||||
),
|
|
||||||
'subscription_change_payment_method' => array(
|
|
||||||
'endpoint' => \WC_AJAX::get_endpoint( SubscriptionChangePaymentMethod::ENDPOINT ),
|
|
||||||
'nonce' => wp_create_nonce( SubscriptionChangePaymentMethod::nonce() ),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'labels' => array(
|
|
||||||
'error' => array(
|
|
||||||
'generic' => __(
|
|
||||||
'Something went wrong. Please try again or choose another payment source.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch ( RuntimeException $exception ) {
|
|
||||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
|
||||||
assert( $logger instanceof LoggerInterface );
|
|
||||||
|
|
||||||
$error = $exception->getMessage();
|
|
||||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
|
||||||
$error = $exception->get_details( $error );
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$logger->error( $error );
|
add_action(
|
||||||
}
|
'wc_ajax_' . CreateSetupToken::ENDPOINT,
|
||||||
}
|
static function () use ( $c ) {
|
||||||
);
|
$endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' );
|
||||||
|
assert( $endpoint instanceof CreateSetupToken );
|
||||||
|
|
||||||
add_action(
|
$endpoint->handle_request();
|
||||||
'woocommerce_add_payment_method_form_bottom',
|
|
||||||
function () use ( $c ) {
|
|
||||||
if ( ! is_user_logged_in() || ! is_add_payment_method_page() || ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '<div id="ppc-button-' . esc_attr( PayPalGateway::ID ) . '-save-payment-method"></div>';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
|
||||||
'wc_ajax_' . CreateSetupToken::ENDPOINT,
|
|
||||||
static function () use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' );
|
|
||||||
assert( $endpoint instanceof CreateSetupToken );
|
|
||||||
|
|
||||||
$endpoint->handle_request();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
|
||||||
'wc_ajax_' . CreatePaymentToken::ENDPOINT,
|
|
||||||
static function () use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' );
|
|
||||||
assert( $endpoint instanceof CreatePaymentToken );
|
|
||||||
|
|
||||||
$endpoint->handle_request();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
|
||||||
'wc_ajax_' . CreatePaymentTokenForGuest::ENDPOINT,
|
|
||||||
static function () use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token-for-guest' );
|
|
||||||
assert( $endpoint instanceof CreatePaymentTokenForGuest );
|
|
||||||
|
|
||||||
$endpoint->handle_request();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
add_action(
|
|
||||||
'woocommerce_paypal_payments_before_delete_payment_token',
|
|
||||||
function( string $token_id ) use ( $c ) {
|
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$endpoint = $c->get( 'api.endpoint.payment-tokens' );
|
|
||||||
assert( $endpoint instanceof PaymentTokensEndpoint );
|
|
||||||
|
|
||||||
$endpoint->delete( $token_id );
|
|
||||||
} catch ( RuntimeException $exception ) {
|
|
||||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
|
||||||
assert( $logger instanceof LoggerInterface );
|
|
||||||
|
|
||||||
$error = $exception->getMessage();
|
|
||||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
|
||||||
$error = $exception->get_details( $error );
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$logger->error( $error );
|
add_action(
|
||||||
}
|
'wc_ajax_' . CreatePaymentToken::ENDPOINT,
|
||||||
}
|
static function () use ( $c ) {
|
||||||
);
|
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' );
|
||||||
|
assert( $endpoint instanceof CreatePaymentToken );
|
||||||
|
|
||||||
add_filter(
|
$endpoint->handle_request();
|
||||||
'woocommerce_paypal_payments_credit_card_gateway_supports',
|
}
|
||||||
function( array $supports ) use ( $c ): array {
|
);
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $supports;
|
|
||||||
}
|
|
||||||
$supports[] = 'tokenization';
|
|
||||||
$supports[] = 'add_payment_method';
|
|
||||||
|
|
||||||
return $supports;
|
add_action(
|
||||||
}
|
'wc_ajax_' . CreatePaymentTokenForGuest::ENDPOINT,
|
||||||
);
|
static function () use ( $c ) {
|
||||||
|
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token-for-guest' );
|
||||||
|
assert( $endpoint instanceof CreatePaymentTokenForGuest );
|
||||||
|
|
||||||
add_filter(
|
$endpoint->handle_request();
|
||||||
'woocommerce_paypal_payments_save_payment_methods_eligible',
|
}
|
||||||
function( bool $value ) use ( $c ): bool {
|
);
|
||||||
if ( ! self::vault_enabled( $c ) ) {
|
|
||||||
return $value;
|
add_action(
|
||||||
}
|
'woocommerce_paypal_payments_before_delete_payment_token',
|
||||||
return true;
|
function( string $token_id ) use ( $c ) {
|
||||||
|
try {
|
||||||
|
$endpoint = $c->get( 'api.endpoint.payment-tokens' );
|
||||||
|
assert( $endpoint instanceof PaymentTokensEndpoint );
|
||||||
|
|
||||||
|
$endpoint->delete( $token_id );
|
||||||
|
} catch ( RuntimeException $exception ) {
|
||||||
|
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
assert( $logger instanceof LoggerInterface );
|
||||||
|
|
||||||
|
$error = $exception->getMessage();
|
||||||
|
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||||
|
$error = $exception->get_details( $error );
|
||||||
|
}
|
||||||
|
|
||||||
|
$logger->error( $error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_paypal_payments_credit_card_gateway_supports',
|
||||||
|
function( array $supports ) use ( $c ): array {
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof ContainerInterface );
|
||||||
|
|
||||||
|
if ( $settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' ) ) {
|
||||||
|
$supports[] = 'tokenization';
|
||||||
|
$supports[] = 'add_payment_method';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $supports;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_paypal_payments_save_payment_methods_eligible',
|
||||||
|
function() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -502,24 +488,4 @@ class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, Execut
|
||||||
|
|
||||||
return $localized_script_data;
|
return $localized_script_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the vault functionality is enabled based on configuration settings.
|
|
||||||
*
|
|
||||||
* @param ContainerInterface $container The dependency injection container from which settings can be retrieved.
|
|
||||||
*
|
|
||||||
* @return bool Returns true if either 'vault_enabled' or 'vault_enabled_dcc' settings are enabled; otherwise, false.
|
|
||||||
*/
|
|
||||||
private static function vault_enabled( ContainerInterface $container ): bool {
|
|
||||||
$settings = $container->get( 'wcgateway.settings' );
|
|
||||||
assert( $settings instanceof Settings );
|
|
||||||
|
|
||||||
if (
|
|
||||||
( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) )
|
|
||||||
&& ( ! $settings->has( 'vault_enabled_dcc' ) || ! $settings->get( 'vault_enabled_dcc' ) )
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo List and Feature Items
|
// Todo List and Feature Items
|
||||||
.ppcp-r-tab-overview-todo {
|
|
||||||
margin: 0 0 48px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ppcp-r-todo-item {
|
.ppcp-r-todo-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -101,6 +97,8 @@
|
||||||
span {
|
span {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
margin-top:24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +229,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings Card and Block Styles
|
// Settings Card and Block Styles
|
||||||
|
.ppcp-r-settings-card {
|
||||||
|
margin: 0 0 48px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ppcp-r-settings-card__content {
|
.ppcp-r-settings-card__content {
|
||||||
> .ppcp-r-settings-block {
|
> .ppcp-r-settings-block {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
export { default as openSignup } from './Icons/open-signup';
|
export { default as openSignup } from './Icons/open-signup';
|
||||||
|
export const NOTIFICATION_SUCCESS = '✔️';
|
||||||
|
export const NOTIFICATION_ERROR = '❌';
|
||||||
|
|
|
@ -5,26 +5,29 @@ import { countryPriceInfo } from '../../utils/countryPriceInfo';
|
||||||
import { formatPrice } from '../../utils/formatPrice';
|
import { formatPrice } from '../../utils/formatPrice';
|
||||||
import TitleBadge, { TITLE_BADGE_INFO } from './TitleBadge';
|
import TitleBadge, { TITLE_BADGE_INFO } from './TitleBadge';
|
||||||
|
|
||||||
const getFixedAmount = ( currency, priceList ) => {
|
const getFixedAmount = ( currency, priceList, itemFixedAmount ) => {
|
||||||
if ( priceList[ currency ] ) {
|
if ( priceList[ currency ] ) {
|
||||||
return formatPrice( priceList[ currency ], currency );
|
const sum = priceList[ currency ] + itemFixedAmount;
|
||||||
|
return formatPrice( sum, currency );
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ defaultCurrency, defaultPrice ] = Object.entries( priceList )[ 0 ];
|
const [ defaultCurrency, defaultPrice ] = Object.entries( priceList )[ 0 ];
|
||||||
|
const sum = defaultPrice + itemFixedAmount;
|
||||||
return formatPrice( defaultPrice, defaultCurrency );
|
return formatPrice( sum, defaultCurrency );
|
||||||
};
|
};
|
||||||
|
|
||||||
const PricingTitleBadge = ( { item } ) => {
|
const PricingTitleBadge = ( { item } ) => {
|
||||||
const { storeCountry } = CommonHooks.useWooSettings();
|
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
||||||
const infos = countryPriceInfo[ storeCountry ];
|
const infos = countryPriceInfo[ storeCountry ];
|
||||||
|
const itemKey = item.split(' ')[0]; // Extract the first word, fastlane has more than one
|
||||||
|
|
||||||
if ( ! infos || ! infos[ item ] ) {
|
if ( ! infos || ! infos[ itemKey ] ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentage = infos[ item ].toFixed( 2 );
|
const percentage = typeof infos[itemKey] === 'number' ? infos[itemKey].toFixed(2) : infos[itemKey]['percentage'].toFixed(2);
|
||||||
const fixedAmount = getFixedAmount( storeCountry, infos.fixedFee );
|
const itemFixedAmount = infos[itemKey]['fixedFee'] ? infos[itemKey]['fixedFee'] : 0;
|
||||||
|
const fixedAmount = getFixedAmount( storeCurrency, infos.fixedFee, itemFixedAmount );
|
||||||
|
|
||||||
const label = sprintf(
|
const label = sprintf(
|
||||||
__( 'from %1$s%% + %2$s', 'woocommerce-paypal-payments' ),
|
__( 'from %1$s%% + %2$s', 'woocommerce-paypal-payments' ),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import SettingsBlock from './SettingsBlock';
|
import SettingsBlock from './SettingsBlock';
|
||||||
import { Header, Title, Action, Description } from './SettingsBlockElements';
|
import { Action, Description, Header, Title } from './SettingsBlockElements';
|
||||||
|
|
||||||
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
|
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
|
||||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
|
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
|
||||||
|
@ -10,6 +10,7 @@ const ButtonSettingsBlock = ( { title, description, ...props } ) => (
|
||||||
</Header>
|
</Header>
|
||||||
<Action>
|
<Action>
|
||||||
<Button
|
<Button
|
||||||
|
isBusy={ props.actionProps?.isBusy }
|
||||||
variant={ props.actionProps?.buttonType }
|
variant={ props.actionProps?.buttonType }
|
||||||
onClick={
|
onClick={
|
||||||
props.actionProps?.callback
|
props.actionProps?.callback
|
||||||
|
|
|
@ -37,6 +37,7 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
|
||||||
<div className="ppcp-r-feature-item__buttons">
|
<div className="ppcp-r-feature-item__buttons">
|
||||||
{ props.actionProps?.buttons.map( ( button ) => (
|
{ props.actionProps?.buttons.map( ( button ) => (
|
||||||
<Button
|
<Button
|
||||||
|
className={ button.class ? button.class : '' }
|
||||||
href={ button.url }
|
href={ button.url }
|
||||||
key={ button.text }
|
key={ button.text }
|
||||||
variant={ button.type }
|
variant={ button.type }
|
||||||
|
|
|
@ -53,6 +53,9 @@ const AcdcFlow = ( { isFastlane, isPayLater, storeCountry } ) => {
|
||||||
imageBadge={ [
|
imageBadge={ [
|
||||||
'icon-payment-method-paypal-small.svg',
|
'icon-payment-method-paypal-small.svg',
|
||||||
] }
|
] }
|
||||||
|
textBadge={
|
||||||
|
<PricingTitleBadge item="plater" />
|
||||||
|
}
|
||||||
description={ sprintf(
|
description={ sprintf(
|
||||||
// translators: %s: Link to PayPal business fees guide
|
// translators: %s: Link to PayPal business fees guide
|
||||||
__(
|
__(
|
||||||
|
|
|
@ -72,16 +72,16 @@ const TabOverview = () => {
|
||||||
className="ppcp-r-tab-overview-features"
|
className="ppcp-r-tab-overview-features"
|
||||||
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
||||||
description={
|
description={
|
||||||
<div>
|
<>
|
||||||
<p>
|
<p>
|
||||||
{ __(
|
{ __(
|
||||||
'Enable additional features…',
|
'Enable additional features and capabilities on your WooCommerce store.',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{ __(
|
{ __(
|
||||||
'Click Refresh…',
|
'Click Refresh to update your current features after making changes.',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
|
@ -101,7 +101,7 @@ const TabOverview = () => {
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</>
|
||||||
}
|
}
|
||||||
contentItems={ features.map( ( feature ) => (
|
contentItems={ features.map( ( feature ) => (
|
||||||
<FeatureSettingsBlock
|
<FeatureSettingsBlock
|
||||||
|
@ -125,6 +125,60 @@ const TabOverview = () => {
|
||||||
/>
|
/>
|
||||||
) ) }
|
) ) }
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SettingsCard
|
||||||
|
className="ppcp-r-tab-overview-help"
|
||||||
|
title={ __( 'Help Center', 'woocommerce-paypal-payments' ) }
|
||||||
|
description={ __(
|
||||||
|
'Access detailed guides and responsive support to streamline setup and enhance your experience.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
contentItems={ [
|
||||||
|
<FeatureSettingsBlock
|
||||||
|
key="documentation"
|
||||||
|
title={ __(
|
||||||
|
'Documentation',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
description={ __(
|
||||||
|
'Find detailed guides and resources to help you set up, manage, and optimize your PayPal integration.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: 'tertiary',
|
||||||
|
text: __(
|
||||||
|
'View full documentation',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
url: '#',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} }
|
||||||
|
/>,
|
||||||
|
<FeatureSettingsBlock
|
||||||
|
key="support"
|
||||||
|
title={ __( 'Support', 'woocommerce-paypal-payments' ) }
|
||||||
|
description={ __(
|
||||||
|
'Need help? Access troubleshooting tips or contact our support team for personalized assistance.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: 'tertiary',
|
||||||
|
text: __(
|
||||||
|
'View support options',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
url: '#',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} }
|
||||||
|
/>,
|
||||||
|
] }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -177,6 +231,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
|
@ -200,6 +255,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
|
@ -223,6 +279,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __( 'Apply', 'woocommerce-paypal-payments' ),
|
text: __( 'Apply', 'woocommerce-paypal-payments' ),
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
|
@ -243,6 +300,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
|
@ -252,9 +310,6 @@ const featuresDefault = [
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
notes: [
|
|
||||||
__( '¹PayPal Q2 Earnings-2021.', 'woocommerce-paypal-payments' ),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'apple_pay',
|
id: 'apple_pay',
|
||||||
|
@ -266,6 +321,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __(
|
text: __(
|
||||||
'Domain registration',
|
'Domain registration',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
|
@ -289,6 +345,7 @@ const featuresDefault = [
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
|
class: 'small-button',
|
||||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
|
@ -298,6 +355,9 @@ const featuresDefault = [
|
||||||
url: '#',
|
url: '#',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
notes: [
|
||||||
|
__( '¹PayPal Q2 Earnings-2021.', 'woocommerce-paypal-payments' ),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import {
|
|
||||||
Header,
|
|
||||||
Title,
|
|
||||||
Description,
|
|
||||||
AccordionSettingsBlock,
|
|
||||||
ToggleSettingsBlock,
|
|
||||||
ButtonSettingsBlock,
|
|
||||||
} from '../../../../ReusableComponents/SettingsBlocks';
|
|
||||||
import SettingsBlock from '../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
|
|
||||||
|
|
||||||
const Troubleshooting = ( { updateFormValue, settings } ) => {
|
|
||||||
return (
|
|
||||||
<AccordionSettingsBlock
|
|
||||||
className="ppcp-r-settings-block--troubleshooting"
|
|
||||||
title={ __( 'Troubleshooting', 'woocommerce-paypal-payments' ) }
|
|
||||||
description={ __(
|
|
||||||
'Access tools to help debug and resolve issues.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
actionProps={ {
|
|
||||||
callback: updateFormValue,
|
|
||||||
key: 'payNowExperience',
|
|
||||||
value: settings.payNowExperience,
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
<ToggleSettingsBlock
|
|
||||||
title={ __( 'Logging', 'woocommerce-paypal-payments' ) }
|
|
||||||
description={ __(
|
|
||||||
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
actionProps={ {
|
|
||||||
callback: updateFormValue,
|
|
||||||
key: 'logging',
|
|
||||||
value: settings.logging,
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
<SettingsBlock>
|
|
||||||
<Header>
|
|
||||||
<Title>
|
|
||||||
{ __(
|
|
||||||
'Subscribed PayPal webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
</Title>
|
|
||||||
<Description>
|
|
||||||
{ __(
|
|
||||||
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }{ ' ' }
|
|
||||||
<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status">
|
|
||||||
{ __(
|
|
||||||
'Webhook Status documentation',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</Description>
|
|
||||||
</Header>
|
|
||||||
<HooksTable data={ hooksExampleData() } />
|
|
||||||
</SettingsBlock>
|
|
||||||
|
|
||||||
<ButtonSettingsBlock
|
|
||||||
title={ __(
|
|
||||||
'Resubscribe webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
description={ __(
|
|
||||||
'Click to remove the current webhook subscription and subscribe again, for example, if the website domain or URL structure changed.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
actionProps={ {
|
|
||||||
buttonType: 'secondary',
|
|
||||||
callback: () =>
|
|
||||||
console.log(
|
|
||||||
'Resubscribe webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
value: __(
|
|
||||||
'Resubscribe webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ButtonSettingsBlock
|
|
||||||
title={ __(
|
|
||||||
'Simulate webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
actionProps={ {
|
|
||||||
buttonType: 'secondary',
|
|
||||||
callback: () =>
|
|
||||||
console.log(
|
|
||||||
'Simulate webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
value: __(
|
|
||||||
'Simulate webhooks',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
</AccordionSettingsBlock>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hooksExampleData = () => {
|
|
||||||
return {
|
|
||||||
url: 'https://www.rt3.tech/wordpress/paypal-ux-testin/index.php?rest_route=/paypal/v1/incoming',
|
|
||||||
hooks: [
|
|
||||||
'billing plan pricing-change activated',
|
|
||||||
'billing plan updated',
|
|
||||||
'billing subscription cancelled',
|
|
||||||
'catalog product updated',
|
|
||||||
'checkout order approved',
|
|
||||||
'checkout order completed',
|
|
||||||
'checkout payment-approval reversed',
|
|
||||||
'payment authorization voided',
|
|
||||||
'payment capture completed',
|
|
||||||
'payment capture denied',
|
|
||||||
'payment capture pending',
|
|
||||||
'payment capture refunded',
|
|
||||||
'payment capture reversed',
|
|
||||||
'payment order cancelled',
|
|
||||||
'payment sale completed',
|
|
||||||
'payment sale refunded',
|
|
||||||
'vault payment-token created',
|
|
||||||
'vault payment-token deleted',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const HooksTable = ( { data } ) => {
|
|
||||||
return (
|
|
||||||
<table className="ppcp-r-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="ppcp-r-table__hooks-url">
|
|
||||||
{ __( 'URL', 'woocommerce-paypal-payments' ) }
|
|
||||||
</th>
|
|
||||||
<th className="ppcp-r-table__hooks-events">
|
|
||||||
{ __(
|
|
||||||
'Tracked events',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td className="ppcp-r-table__hooks-url">{ data?.url }</td>
|
|
||||||
<td className="ppcp-r-table__hooks-events">
|
|
||||||
{ data.hooks.map( ( hook, index ) => (
|
|
||||||
<span key={ hook }>
|
|
||||||
{ hook }{ ' ' }
|
|
||||||
{ index !== data.hooks.length - 1 && ',' }
|
|
||||||
</span>
|
|
||||||
) ) }
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Troubleshooting;
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { CommonHooks } from '../../../../../../data';
|
||||||
|
|
||||||
|
const HooksTableBlock = () => {
|
||||||
|
const { webhooks } = CommonHooks.useWebhooks();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className="ppcp-r-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="ppcp-r-table__hooks-url">
|
||||||
|
{ __( 'URL', 'woocommerce-paypal-payments' ) }
|
||||||
|
</th>
|
||||||
|
<th className="ppcp-r-table__hooks-events">
|
||||||
|
{ __(
|
||||||
|
'Tracked events',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="ppcp-r-table__hooks-url">
|
||||||
|
{ webhooks?.url }
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="ppcp-r-table__hooks-events"
|
||||||
|
dangerouslySetInnerHTML={ { __html: webhooks?.events } }
|
||||||
|
></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HooksTableBlock;
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { useState } from '@wordpress/element';
|
||||||
|
import { STORE_NAME } from '../../../../../../data/common';
|
||||||
|
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import {
|
||||||
|
NOTIFICATION_ERROR,
|
||||||
|
NOTIFICATION_SUCCESS,
|
||||||
|
} from '../../../../../ReusableComponents/Icons';
|
||||||
|
|
||||||
|
const ResubscribeBlock = () => {
|
||||||
|
const { createSuccessNotice, createErrorNotice } =
|
||||||
|
useDispatch( noticesStore );
|
||||||
|
const [ resubscribing, setResubscribing ] = useState( false );
|
||||||
|
|
||||||
|
const { resubscribeWebhooks } = useDispatch( STORE_NAME );
|
||||||
|
|
||||||
|
const startResubscribingWebhooks = async () => {
|
||||||
|
setResubscribing( true );
|
||||||
|
try {
|
||||||
|
await resubscribeWebhooks();
|
||||||
|
} catch ( error ) {
|
||||||
|
setResubscribing( false );
|
||||||
|
createErrorNotice(
|
||||||
|
__(
|
||||||
|
'Operation failed. Check WooCommerce logs for more details.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
icon: NOTIFICATION_ERROR,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResubscribing( false );
|
||||||
|
createSuccessNotice(
|
||||||
|
__(
|
||||||
|
'Webhooks were successfully re-subscribed.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
icon: NOTIFICATION_SUCCESS,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSettingsBlock
|
||||||
|
title={ __(
|
||||||
|
'Resubscribe webhooks',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
description={ __(
|
||||||
|
'Click to remove the current webhook subscription and subscribe again, for example, if the website domain or URL structure changed.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
buttonType: 'secondary',
|
||||||
|
isBusy: resubscribing,
|
||||||
|
callback: () => startResubscribingWebhooks(),
|
||||||
|
value: __(
|
||||||
|
'Resubscribe webhooks',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResubscribeBlock;
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { useState } from '@wordpress/element';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import { CommonHooks } from '../../../../../../data';
|
||||||
|
import {
|
||||||
|
NOTIFICATION_ERROR,
|
||||||
|
NOTIFICATION_SUCCESS,
|
||||||
|
} from '../../../../../ReusableComponents/Icons';
|
||||||
|
|
||||||
|
const SimulationBlock = () => {
|
||||||
|
const {
|
||||||
|
createSuccessNotice,
|
||||||
|
createInfoNotice,
|
||||||
|
createErrorNotice,
|
||||||
|
removeNotice,
|
||||||
|
} = useDispatch( noticesStore );
|
||||||
|
const { startWebhookSimulation, checkWebhookSimulationState } =
|
||||||
|
CommonHooks.useWebhooks();
|
||||||
|
const [ simulating, setSimulating ] = useState( false );
|
||||||
|
const sleep = ( ms ) => {
|
||||||
|
return new Promise( ( resolve ) => setTimeout( resolve, ms ) );
|
||||||
|
};
|
||||||
|
const startSimulation = async ( maxRetries ) => {
|
||||||
|
const webhookInfoNoticeId = 'paypal-webhook-simulation-info-notice';
|
||||||
|
const triggerWebhookInfoNotice = () => {
|
||||||
|
createInfoNotice(
|
||||||
|
__(
|
||||||
|
'Waiting for the webhook to arrive…',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: webhookInfoNoticeId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopSimulation = () => {
|
||||||
|
removeNotice( webhookInfoNoticeId );
|
||||||
|
setSimulating( false );
|
||||||
|
};
|
||||||
|
|
||||||
|
setSimulating( true );
|
||||||
|
|
||||||
|
triggerWebhookInfoNotice();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await startWebhookSimulation();
|
||||||
|
} catch ( error ) {
|
||||||
|
console.error( error );
|
||||||
|
setSimulating( false );
|
||||||
|
createErrorNotice(
|
||||||
|
__(
|
||||||
|
'Operation failed. Check WooCommerce logs for more details.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
icon: NOTIFICATION_ERROR,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( let i = 0; i < maxRetries; i++ ) {
|
||||||
|
await sleep( 2000 );
|
||||||
|
|
||||||
|
const simulationStateResponse = await checkWebhookSimulationState();
|
||||||
|
try {
|
||||||
|
if ( ! simulationStateResponse.success ) {
|
||||||
|
console.error(
|
||||||
|
'Simulation state query failed: ' +
|
||||||
|
simulationStateResponse?.data
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( simulationStateResponse?.data?.state === 'received' ) {
|
||||||
|
createSuccessNotice(
|
||||||
|
__(
|
||||||
|
'The webhook was received successfully.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
icon: NOTIFICATION_SUCCESS,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
stopSimulation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeNotice( webhookInfoNoticeId );
|
||||||
|
triggerWebhookInfoNotice();
|
||||||
|
} catch ( error ) {
|
||||||
|
console.error( error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopSimulation();
|
||||||
|
createErrorNotice(
|
||||||
|
__(
|
||||||
|
'Looks like the webhook cannot be received. Check that your website is accessible from the internet.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
icon: NOTIFICATION_ERROR,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonSettingsBlock
|
||||||
|
title={ __(
|
||||||
|
'Simulate webhooks',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
buttonType: 'secondary',
|
||||||
|
isBusy: simulating,
|
||||||
|
callback: () => startSimulation( 30 ),
|
||||||
|
value: __(
|
||||||
|
'Simulate webhooks',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default SimulationBlock;
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import {
|
||||||
|
AccordionSettingsBlock,
|
||||||
|
Description,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
ToggleSettingsBlock,
|
||||||
|
} from '../../../../../ReusableComponents/SettingsBlocks';
|
||||||
|
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
|
||||||
|
|
||||||
|
import SimulationBlock from './SimulationBlock';
|
||||||
|
import ResubscribeBlock from './ResubscribeBlock';
|
||||||
|
import HooksTableBlock from './HooksTableBlock';
|
||||||
|
|
||||||
|
const Troubleshooting = ( { updateFormValue, settings } ) => {
|
||||||
|
return (
|
||||||
|
<AccordionSettingsBlock
|
||||||
|
className="ppcp-r-settings-block--troubleshooting"
|
||||||
|
title={ __( 'Troubleshooting', 'woocommerce-paypal-payments' ) }
|
||||||
|
description={ __(
|
||||||
|
'Access tools to help debug and resolve issues.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
callback: updateFormValue,
|
||||||
|
key: 'payNowExperience',
|
||||||
|
value: settings.payNowExperience,
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
<ToggleSettingsBlock
|
||||||
|
title={ __( 'Logging', 'woocommerce-paypal-payments' ) }
|
||||||
|
description={ __(
|
||||||
|
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
actionProps={ {
|
||||||
|
callback: updateFormValue,
|
||||||
|
key: 'logging',
|
||||||
|
value: settings.logging,
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
<SettingsBlock>
|
||||||
|
<Header>
|
||||||
|
<Title>
|
||||||
|
{ __(
|
||||||
|
'Subscribed PayPal webhooks',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
</Title>
|
||||||
|
<Description>
|
||||||
|
{ __(
|
||||||
|
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }{ ' ' }
|
||||||
|
<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status">
|
||||||
|
{ __(
|
||||||
|
'Webhook Status documentation',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</Description>
|
||||||
|
</Header>
|
||||||
|
<HooksTableBlock />
|
||||||
|
<ResubscribeBlock />
|
||||||
|
<SimulationBlock />
|
||||||
|
</SettingsBlock>
|
||||||
|
</AccordionSettingsBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Troubleshooting;
|
|
@ -5,7 +5,7 @@ import {
|
||||||
ContentWrapper,
|
ContentWrapper,
|
||||||
} from '../../../ReusableComponents/SettingsBlocks';
|
} from '../../../ReusableComponents/SettingsBlocks';
|
||||||
import Sandbox from './Blocks/Sandbox';
|
import Sandbox from './Blocks/Sandbox';
|
||||||
import Troubleshooting from './Blocks/Troubleshooting';
|
import Troubleshooting from './Blocks/Troubleshooting/Troubleshooting';
|
||||||
import PaypalSettings from './Blocks/PaypalSettings';
|
import PaypalSettings from './Blocks/PaypalSettings';
|
||||||
import OtherSettings from './Blocks/OtherSettings';
|
import OtherSettings from './Blocks/OtherSettings';
|
||||||
|
|
||||||
|
|
|
@ -24,4 +24,8 @@ export default {
|
||||||
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
|
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
|
||||||
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
|
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
|
||||||
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
|
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
|
||||||
|
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
|
||||||
|
DO_START_WEBHOOK_SIMULATION: 'COMMON:DO_START_WEBHOOK_SIMULATION',
|
||||||
|
DO_CHECK_WEBHOOK_SIMULATION_STATE:
|
||||||
|
'COMMON:DO_CHECK_WEBHOOK_SIMULATION_STATE',
|
||||||
};
|
};
|
||||||
|
|
|
@ -250,3 +250,48 @@ export const refreshFeatureStatuses = function* () {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistent. Changes the "webhooks" value.
|
||||||
|
*
|
||||||
|
* @param {string} webhooks
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const setWebhooks = ( webhooks ) => ( {
|
||||||
|
type: ACTION_TYPES.SET_PERSISTENT,
|
||||||
|
payload: { webhooks },
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect
|
||||||
|
* Refreshes subscribed webhooks via a REST request
|
||||||
|
*
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const resubscribeWebhooks = function* () {
|
||||||
|
const result = yield { type: ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS };
|
||||||
|
|
||||||
|
if ( result && result.success ) {
|
||||||
|
yield hydrate( result );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect. Starts webhook simulation.
|
||||||
|
*
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const startWebhookSimulation = function* () {
|
||||||
|
return yield { type: ACTION_TYPES.DO_START_WEBHOOK_SIMULATION };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect. Checks webhook simulation.
|
||||||
|
*
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const checkWebhookSimulationState = function* () {
|
||||||
|
return yield { type: ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION_STATE };
|
||||||
|
};
|
||||||
|
|
|
@ -65,6 +65,26 @@ export const REST_ISU_AUTHENTICATION_PATH = '/wc/v3/wc_paypal/authenticate/isu';
|
||||||
*/
|
*/
|
||||||
export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link';
|
export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST path to fetch webhooks data or resubscribe webhooks,
|
||||||
|
*
|
||||||
|
* Used by: Controls
|
||||||
|
* See: WebhookSettingsEndpoint.php
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const REST_WEBHOOKS = '/wc/v3/wc_paypal/webhook_settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST path to start webhook simulation and observe the state,
|
||||||
|
*
|
||||||
|
* Used by: Controls
|
||||||
|
* See: WebhookSettingsEndpoint.php
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhook_simulate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST path to refresh the feature status.
|
* REST path to refresh the feature status.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {
|
||||||
REST_HYDRATE_MERCHANT_PATH,
|
REST_HYDRATE_MERCHANT_PATH,
|
||||||
REST_REFRESH_FEATURES_PATH,
|
REST_REFRESH_FEATURES_PATH,
|
||||||
REST_ISU_AUTHENTICATION_PATH,
|
REST_ISU_AUTHENTICATION_PATH,
|
||||||
|
REST_WEBHOOKS,
|
||||||
|
REST_WEBHOOKS_SIMULATE,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
@ -121,4 +123,24 @@ export const controls = {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async [ ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS ]() {
|
||||||
|
return await apiFetch( {
|
||||||
|
method: 'POST',
|
||||||
|
path: REST_WEBHOOKS,
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
async [ ACTION_TYPES.DO_START_WEBHOOK_SIMULATION ]() {
|
||||||
|
return await apiFetch( {
|
||||||
|
method: 'POST',
|
||||||
|
path: REST_WEBHOOKS_SIMULATE,
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
async [ ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION_STATE ]() {
|
||||||
|
return await apiFetch( {
|
||||||
|
path: REST_WEBHOOKS_SIMULATE,
|
||||||
|
} );
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
import { useCallback } from '@wordpress/element';
|
import { useCallback } from '@wordpress/element';
|
||||||
|
|
||||||
import { STORE_NAME } from './constants';
|
import { STORE_NAME } from './constants';
|
||||||
|
|
||||||
const useTransient = ( key ) =>
|
const useTransient = ( key ) =>
|
||||||
|
@ -35,6 +34,8 @@ const useHooks = () => {
|
||||||
productionOnboardingUrl,
|
productionOnboardingUrl,
|
||||||
connectViaSecret,
|
connectViaSecret,
|
||||||
connectViaAuthCode,
|
connectViaAuthCode,
|
||||||
|
startWebhookSimulation,
|
||||||
|
checkWebhookSimulationState,
|
||||||
} = useDispatch( STORE_NAME );
|
} = useDispatch( STORE_NAME );
|
||||||
|
|
||||||
// Transient accessors.
|
// Transient accessors.
|
||||||
|
@ -45,7 +46,7 @@ const useHooks = () => {
|
||||||
const clientSecret = usePersistent( 'clientSecret' );
|
const clientSecret = usePersistent( 'clientSecret' );
|
||||||
const isSandboxMode = usePersistent( 'useSandbox' );
|
const isSandboxMode = usePersistent( 'useSandbox' );
|
||||||
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
||||||
|
const webhooks = usePersistent( 'webhooks' );
|
||||||
const merchant = useSelect(
|
const merchant = useSelect(
|
||||||
( select ) => select( STORE_NAME ).merchant(),
|
( select ) => select( STORE_NAME ).merchant(),
|
||||||
[]
|
[]
|
||||||
|
@ -84,6 +85,9 @@ const useHooks = () => {
|
||||||
connectViaAuthCode,
|
connectViaAuthCode,
|
||||||
merchant,
|
merchant,
|
||||||
wooSettings,
|
wooSettings,
|
||||||
|
webhooks,
|
||||||
|
startWebhookSimulation,
|
||||||
|
checkWebhookSimulationState,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -129,6 +133,22 @@ export const useWooSettings = () => {
|
||||||
return wooSettings;
|
return wooSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useWebhooks = () => {
|
||||||
|
const {
|
||||||
|
webhooks,
|
||||||
|
setWebhooks,
|
||||||
|
registerWebhooks,
|
||||||
|
startWebhookSimulation,
|
||||||
|
checkWebhookSimulationState,
|
||||||
|
} = useHooks();
|
||||||
|
return {
|
||||||
|
webhooks,
|
||||||
|
setWebhooks,
|
||||||
|
registerWebhooks,
|
||||||
|
startWebhookSimulation,
|
||||||
|
checkWebhookSimulationState,
|
||||||
|
};
|
||||||
|
};
|
||||||
export const useMerchantInfo = () => {
|
export const useMerchantInfo = () => {
|
||||||
const { merchant } = useHooks();
|
const { merchant } = useHooks();
|
||||||
const { refreshMerchantData } = useDispatch( STORE_NAME );
|
const { refreshMerchantData } = useDispatch( STORE_NAME );
|
||||||
|
|
|
@ -35,6 +35,7 @@ const defaultPersistent = Object.freeze( {
|
||||||
useManualConnection: false,
|
useManualConnection: false,
|
||||||
clientId: '',
|
clientId: '',
|
||||||
clientSecret: '',
|
clientSecret: '',
|
||||||
|
webhooks: [],
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Reducer logic.
|
// Reducer logic.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { dispatch } from '@wordpress/data';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { apiFetch } from '@wordpress/data-controls';
|
import { apiFetch } from '@wordpress/data-controls';
|
||||||
|
|
||||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
import { STORE_NAME, REST_HYDRATE_PATH, REST_WEBHOOKS } from './constants';
|
||||||
|
|
||||||
export const resolvers = {
|
export const resolvers = {
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,9 @@ export const resolvers = {
|
||||||
*persistentData() {
|
*persistentData() {
|
||||||
try {
|
try {
|
||||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||||
|
const webhooks = yield apiFetch( { path: REST_WEBHOOKS } );
|
||||||
|
|
||||||
|
result.data = { ...result.data, ...webhooks.data };
|
||||||
|
|
||||||
yield dispatch( STORE_NAME ).hydrate( result );
|
yield dispatch( STORE_NAME ).hydrate( result );
|
||||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||||
|
|
|
@ -33,3 +33,7 @@ export const merchant = ( state ) => {
|
||||||
export const wooSettings = ( state ) => {
|
export const wooSettings = ( state ) => {
|
||||||
return getState( state ).wooSettings || EMPTY_OBJ;
|
return getState( state ).wooSettings || EMPTY_OBJ;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const webhooks = ( state ) => {
|
||||||
|
return getState( state ).webhooks || EMPTY_OBJ;
|
||||||
|
};
|
||||||
|
|
|
@ -2,81 +2,139 @@ export const countryPriceInfo = {
|
||||||
US: {
|
US: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
USD: 0.49,
|
USD: 0.49,
|
||||||
|
GBP: 0.39,
|
||||||
|
CAD: 0.59,
|
||||||
|
AUD: 0.59,
|
||||||
|
EUR: 0.39,
|
||||||
},
|
},
|
||||||
checkout: 3.49,
|
checkout: 3.49,
|
||||||
ccf: 2.59,
|
plater: 4.99,
|
||||||
dw: 2.59,
|
ccf: {
|
||||||
apm: 2.59,
|
percentage: 2.59,
|
||||||
fastlane: 2.59,
|
fixedFee: 0.29,
|
||||||
|
},
|
||||||
|
dw: {
|
||||||
|
percentage: 2.59,
|
||||||
|
fixedFee: 0.29,
|
||||||
|
},
|
||||||
|
apm: {
|
||||||
|
percentage: 2.89,
|
||||||
|
fixedFee: 0.29,
|
||||||
|
},
|
||||||
|
fast: {
|
||||||
|
percentage: 2.59,
|
||||||
|
fixedFee: 0.29,
|
||||||
|
},
|
||||||
standardCardFields: 2.99,
|
standardCardFields: 2.99,
|
||||||
},
|
},
|
||||||
UK: {
|
UK: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
GPB: 0.3,
|
GPB: 0.3,
|
||||||
|
USD: 0.3,
|
||||||
|
CAD: 0.3,
|
||||||
|
AUD: 0.3,
|
||||||
|
EUR: 0.35,
|
||||||
},
|
},
|
||||||
checkout: 2.9,
|
checkout: 2.9,
|
||||||
|
plater: 2.9,
|
||||||
ccf: 1.2,
|
ccf: 1.2,
|
||||||
dw: 1.2,
|
dw: 1.2,
|
||||||
|
fast: 1.2,
|
||||||
apm: 1.2,
|
apm: 1.2,
|
||||||
standardCardFields: 1.2,
|
standardCardFields: 1.2,
|
||||||
},
|
},
|
||||||
CA: {
|
CA: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
CAD: 0.3,
|
CAD: 0.3,
|
||||||
|
USD: 0.3,
|
||||||
|
GBP: 0.2,
|
||||||
|
AUD: 0.3,
|
||||||
|
EUR: 0.35,
|
||||||
},
|
},
|
||||||
checkout: 2.9,
|
checkout: 2.9,
|
||||||
ccf: 2.7,
|
ccf: 2.7,
|
||||||
dw: 2.7,
|
dw: 2.7,
|
||||||
|
fast: 2.7,
|
||||||
apm: 2.9,
|
apm: 2.9,
|
||||||
standardCardFields: 2.9,
|
standardCardFields: 2.9,
|
||||||
},
|
},
|
||||||
AU: {
|
AU: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
AUD: 0.3,
|
AUD: 0.3,
|
||||||
|
USD: 0.3,
|
||||||
|
GBP: 0.2,
|
||||||
|
CAD: 0.3,
|
||||||
|
EUR: 0.35,
|
||||||
},
|
},
|
||||||
checkout: 2.6,
|
checkout: 2.6,
|
||||||
|
plater: 2.6,
|
||||||
ccf: 1.75,
|
ccf: 1.75,
|
||||||
dw: 1.75,
|
dw: 1.75,
|
||||||
|
fast: 1.75,
|
||||||
apm: 2.6,
|
apm: 2.6,
|
||||||
standardCardFields: 2.6,
|
standardCardFields: 2.6,
|
||||||
},
|
},
|
||||||
FR: {
|
FR: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
EUR: 0.35,
|
EUR: 0.35,
|
||||||
|
USD: 0.3,
|
||||||
|
GBP: 0.3,
|
||||||
|
CAD: 0.3,
|
||||||
|
AUD: 0.3,
|
||||||
},
|
},
|
||||||
checkout: 2.9,
|
checkout: 2.9,
|
||||||
|
plater: 2.9,
|
||||||
ccf: 1.2,
|
ccf: 1.2,
|
||||||
dw: 1.2,
|
dw: 1.2,
|
||||||
|
fast: 1.2,
|
||||||
apm: 1.2,
|
apm: 1.2,
|
||||||
standardCardFields: 1.2,
|
standardCardFields: 1.2,
|
||||||
},
|
},
|
||||||
IT: {
|
IT: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
EUR: 0.35,
|
EUR: 0.35,
|
||||||
|
USD: 0.3,
|
||||||
|
GBP: 0.3,
|
||||||
|
CAD: 0.3,
|
||||||
|
AUD: 0.3,
|
||||||
},
|
},
|
||||||
checkout: 3.4,
|
checkout: 3.4,
|
||||||
|
plater: 3.4,
|
||||||
ccf: 1.2,
|
ccf: 1.2,
|
||||||
dw: 1.2,
|
dw: 1.2,
|
||||||
|
fast: 1.2,
|
||||||
apm: 1.2,
|
apm: 1.2,
|
||||||
standardCardFields: 1.2,
|
standardCardFields: 1.2,
|
||||||
},
|
},
|
||||||
DE: {
|
DE: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
EUR: 0.39,
|
EUR: 0.39,
|
||||||
|
USD: 0.49,
|
||||||
|
GBP: 0.29,
|
||||||
|
CAD: 0.59,
|
||||||
|
AUD: 0.59,
|
||||||
},
|
},
|
||||||
checkout: 2.99,
|
checkout: 2.99,
|
||||||
|
plater: 2.99,
|
||||||
ccf: 2.99,
|
ccf: 2.99,
|
||||||
dw: 2.99,
|
dw: 2.99,
|
||||||
|
fast: 2.99,
|
||||||
apm: 2.99,
|
apm: 2.99,
|
||||||
standardCardFields: 2.99,
|
standardCardFields: 2.99,
|
||||||
},
|
},
|
||||||
ES: {
|
ES: {
|
||||||
fixedFee: {
|
fixedFee: {
|
||||||
EUR: 0.35,
|
EUR: 0.35,
|
||||||
|
USD: 0.3,
|
||||||
|
GBP: 0.3,
|
||||||
|
CAD: 0.3,
|
||||||
|
AUD: 0.3,
|
||||||
},
|
},
|
||||||
checkout: 2.9,
|
checkout: 2.9,
|
||||||
|
plater: 2.9,
|
||||||
ccf: 1.2,
|
ccf: 1.2,
|
||||||
dw: 1.2,
|
dw: 1.2,
|
||||||
|
fast: 1.2,
|
||||||
apm: 1.2,
|
apm: 1.2,
|
||||||
standardCardFields: 1.2,
|
standardCardFields: 1.2,
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,12 +19,12 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\EnvironmentConfig;
|
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'settings.url' => static function ( ContainerInterface $container ) : string {
|
'settings.url' => static function ( ContainerInterface $container ) : string {
|
||||||
|
@ -90,6 +90,13 @@ return array(
|
||||||
$container->get( 'settings.service.connection-url-generators' ),
|
$container->get( 'settings.service.connection-url-generators' ),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
'settings.rest.webhooks' => static function ( ContainerInterface $container ) : WebhookSettingsEndpoint {
|
||||||
|
return new WebhookSettingsEndpoint(
|
||||||
|
$container->get( 'api.endpoint.webhook' ),
|
||||||
|
$container->get( 'webhook.registrar' ),
|
||||||
|
$container->get( 'webhook.status.simulation' )
|
||||||
|
);
|
||||||
|
},
|
||||||
'settings.casual-selling.supported-countries' => static function ( ContainerInterface $container ) : array {
|
'settings.casual-selling.supported-countries' => static function ( ContainerInterface $container ) : array {
|
||||||
return array(
|
return array(
|
||||||
'AR',
|
'AR',
|
||||||
|
|
|
@ -58,6 +58,9 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
'js_name' => 'clientSecret',
|
'js_name' => 'clientSecret',
|
||||||
'sanitize' => 'sanitize_text_field',
|
'sanitize' => 'sanitize_text_field',
|
||||||
),
|
),
|
||||||
|
'webhooks' => array(
|
||||||
|
'js_name' => 'webhooks',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
185
modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php
Normal file
185
modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST endpoint to manage the onboarding module.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||||
|
|
||||||
|
use stdClass;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||||
|
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||||
|
use WP_REST_Response;
|
||||||
|
use WP_REST_Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class WebhookSettingsEndpoint
|
||||||
|
*
|
||||||
|
* Note: Endpoint for webhook related requests
|
||||||
|
*/
|
||||||
|
class WebhookSettingsEndpoint extends RestEndpoint {
|
||||||
|
/**
|
||||||
|
* Endpoint base to fetch webhook settings and resubscribe
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'webhook_settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint base to start webhook simulation and check the state
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $rest_simulate_base = 'webhook_simulate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application webhook endpoint
|
||||||
|
*
|
||||||
|
* @var WebhookEndpoint
|
||||||
|
*/
|
||||||
|
private WebhookEndpoint $webhook_endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that allows resubscribing webhooks
|
||||||
|
*
|
||||||
|
* @var WebhookRegistrar
|
||||||
|
*/
|
||||||
|
private WebhookRegistrar $webhook_registrar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that allows webhook simulations
|
||||||
|
*
|
||||||
|
* @var WebhookSimulation
|
||||||
|
*/
|
||||||
|
private WebhookSimulation $webhook_simulation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebhookSettingsEndpoint constructor.
|
||||||
|
*
|
||||||
|
* @param WebhookEndpoint $webhook_endpoint A list of subscribed webhooks and a webhook endpoint URL.
|
||||||
|
* @param WebhookRegistrar $webhook_registrar A service that allows resubscribing webhooks.
|
||||||
|
* @param WebhookSimulation $webhook_simulation A service that allows webhook simulations.
|
||||||
|
*/
|
||||||
|
public function __construct( WebhookEndpoint $webhook_endpoint, WebhookRegistrar $webhook_registrar, WebhookSimulation $webhook_simulation ) {
|
||||||
|
$this->webhook_endpoint = $webhook_endpoint;
|
||||||
|
$this->webhook_registrar = $webhook_registrar;
|
||||||
|
$this->webhook_simulation = $webhook_simulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure REST API routes.
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
register_rest_route(
|
||||||
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base,
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'get_webhooks' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
'callback' => array( $this, 'resubscribe_webhooks' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_rest_route(
|
||||||
|
$this->namespace,
|
||||||
|
'/' . $this->rest_simulate_base,
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'check_simulated_webhook_state' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
'callback' => array( $this, 'simulate_webhooks_start' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a webhook endpoint URL and list of subscribed webhooks
|
||||||
|
*
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function get_webhooks(): WP_REST_Response {
|
||||||
|
try {
|
||||||
|
$webhook_list = ( $this->webhook_endpoint->list() )[0];
|
||||||
|
$webhook_events = array_map(
|
||||||
|
function ( stdClass $webhook ) {
|
||||||
|
return strtolower( $webhook->name );
|
||||||
|
},
|
||||||
|
$webhook_list->event_types()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->return_success(
|
||||||
|
array(
|
||||||
|
'webhooks' => array(
|
||||||
|
'url' => $webhook_list->url(),
|
||||||
|
'events' => implode( ', ', $webhook_events ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch ( \Exception $error ) {
|
||||||
|
return $this->return_error( 'Problem while fetching webhooks data' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-subscribes webhooks and returns webhooks
|
||||||
|
*
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function resubscribe_webhooks(): WP_REST_Response {
|
||||||
|
if ( ! $this->webhook_registrar->register() ) {
|
||||||
|
return $this->return_error( 'Webhook subscription failed.' );
|
||||||
|
}
|
||||||
|
return $this->get_webhooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts webhook simulation
|
||||||
|
*
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function simulate_webhooks_start(): WP_REST_Response {
|
||||||
|
try {
|
||||||
|
$this->webhook_simulation->start();
|
||||||
|
return $this->return_success( array() );
|
||||||
|
} catch ( \Exception $error ) {
|
||||||
|
return $this->return_error( $error->getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks webhook simulation state
|
||||||
|
*
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function check_simulated_webhook_state(): WP_REST_Response {
|
||||||
|
try {
|
||||||
|
$state = $this->webhook_simulation->get_state();
|
||||||
|
|
||||||
|
return $this->return_success(
|
||||||
|
array(
|
||||||
|
'state' => $state,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch ( \Exception $error ) {
|
||||||
|
return $this->return_error( $error->getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -181,6 +181,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
$container->get( 'settings.rest.common' ),
|
$container->get( 'settings.rest.common' ),
|
||||||
$container->get( 'settings.rest.connect_manual' ),
|
$container->get( 'settings.rest.connect_manual' ),
|
||||||
$container->get( 'settings.rest.login_link' ),
|
$container->get( 'settings.rest.login_link' ),
|
||||||
|
$container->get( 'settings.rest.webhooks' ),
|
||||||
$container->get( 'settings.rest.refresh_feature_status' ),
|
$container->get( 'settings.rest.refresh_feature_status' ),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -466,7 +466,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$custom_id = $wc_order->get_order_number();
|
$custom_id = (string) $wc_order->get_id();
|
||||||
$invoice_id = $this->prefix . $wc_order->get_order_number();
|
$invoice_id = $this->prefix . $wc_order->get_order_number();
|
||||||
$create_order = $this->capture_card_payment->create_order( $token->get_token(), $custom_id, $invoice_id, $wc_order );
|
$create_order = $this->capture_card_payment->create_order( $token->get_token(), $custom_id, $invoice_id, $wc_order );
|
||||||
|
|
||||||
|
|
|
@ -96,52 +96,35 @@ trait CreditCardOrderInfoHandlingTrait {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fraud_responses = $fraud->to_array();
|
|
||||||
$card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
$card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
||||||
$card_last_digits = $payment_source->properties()->last_digits ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
$card_last_digits = $payment_source->properties()->last_digits ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
||||||
|
|
||||||
$avs_response_order_note_title = __( 'Address Verification Result', 'woocommerce-paypal-payments' );
|
$response_order_note_title = __( 'PayPal Advanced Card Processing Verification:', 'woocommerce-paypal-payments' );
|
||||||
/* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
|
/* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
|
||||||
$avs_response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' );
|
$response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' );
|
||||||
$avs_response_order_note_result_format = '<ul class="ppcp_avs_result">
|
$response_order_note_result_format = '<ul class="ppcp_avs_cvv_result">
|
||||||
<li>%1$s</li>
|
<li>%1$s</li>
|
||||||
<ul class="ppcp_avs_result_inner">
|
<li>%2$s</li>
|
||||||
<li>%2$s</li>
|
<li>%3$s</li>
|
||||||
<li>%3$s</li>
|
</ul>';
|
||||||
</ul>
|
$response_order_note_result = sprintf(
|
||||||
<li>%4$s</li>
|
$response_order_note_result_format,
|
||||||
<li>%5$s</li>
|
/* translators: %1$s is card brand and %2$s card last 4 digits */
|
||||||
</ul>';
|
sprintf( __( 'Card: %1$s (%2$s)', 'woocommerce-paypal-payments' ), $card_brand, $card_last_digits ),
|
||||||
$avs_response_order_note_result = sprintf(
|
/* translators: %s is fraud AVS message */
|
||||||
$avs_response_order_note_result_format,
|
sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), $fraud->get_avs_code_message() ),
|
||||||
/* translators: %s is fraud AVS code */
|
/* translators: %s is fraud CVV message */
|
||||||
sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['avs_code'] ) ),
|
sprintf( __( 'CVV: %s', 'woocommerce-paypal-payments' ), $fraud->get_cvv2_code_message() ),
|
||||||
/* translators: %s is fraud AVS address match */
|
|
||||||
sprintf( __( 'Address Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['address_match'] ) ),
|
|
||||||
/* translators: %s is fraud AVS postal match */
|
|
||||||
sprintf( __( 'Postal Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['postal_match'] ) ),
|
|
||||||
/* translators: %s is card brand */
|
|
||||||
sprintf( __( 'Card Brand: %s', 'woocommerce-paypal-payments' ), esc_html( $card_brand ) ),
|
|
||||||
/* translators: %s card last digits */
|
|
||||||
sprintf( __( 'Card Last Digits: %s', 'woocommerce-paypal-payments' ), esc_html( $card_last_digits ) )
|
|
||||||
);
|
);
|
||||||
$avs_response_order_note = sprintf(
|
$response_order_note = sprintf(
|
||||||
$avs_response_order_note_format,
|
$response_order_note_format,
|
||||||
esc_html( $avs_response_order_note_title ),
|
esc_html( $response_order_note_title ),
|
||||||
wp_kses_post( $avs_response_order_note_result )
|
wp_kses_post( $response_order_note_result )
|
||||||
);
|
);
|
||||||
$wc_order->add_order_note( $avs_response_order_note );
|
$wc_order->add_order_note( $response_order_note );
|
||||||
|
|
||||||
$cvv_response_order_note_format = '<ul class="ppcp_cvv_result"><li>%1$s</li></ul>';
|
|
||||||
$cvv_response_order_note = sprintf(
|
|
||||||
$cvv_response_order_note_format,
|
|
||||||
/* translators: %s is fraud CVV match */
|
|
||||||
sprintf( __( 'CVV2 Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['cvv_match'] ) )
|
|
||||||
);
|
|
||||||
$wc_order->add_order_note( $cvv_response_order_note );
|
|
||||||
|
|
||||||
$meta_details = array_merge(
|
$meta_details = array_merge(
|
||||||
$fraud_responses,
|
$fraud->to_array(),
|
||||||
array(
|
array(
|
||||||
'card_brand' => $card_brand,
|
'card_brand' => $card_brand,
|
||||||
'card_last_digits' => $card_last_digits,
|
'card_last_digits' => $card_last_digits,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "woocommerce-paypal-payments",
|
"name": "woocommerce-paypal-payments",
|
||||||
"version": "2.9.5",
|
"version": "2.9.6",
|
||||||
"description": "WooCommerce PayPal Payments",
|
"description": "WooCommerce PayPal Payments",
|
||||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0",
|
||||||
|
|
48
readme.txt
48
readme.txt
|
@ -1,10 +1,10 @@
|
||||||
=== WooCommerce PayPal Payments ===
|
=== WooCommerce PayPal Payments ===
|
||||||
Contributors: woocommerce, automattic, syde
|
Contributors: woocommerce, automattic, syde
|
||||||
Tags: woocommerce, paypal, payments, ecommerce, credit card
|
Tags: woocommerce, paypal, payments, ecommerce, credit card
|
||||||
Requires at least: 6.3
|
Requires at least: 6.5
|
||||||
Tested up to: 6.7
|
Tested up to: 6.7
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Stable tag: 2.9.5
|
Stable tag: 2.9.6
|
||||||
License: GPLv2
|
License: GPLv2
|
||||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
|
||||||
|
@ -179,23 +179,35 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
|
||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 2.9.6 - XXXX-XX-XX =
|
||||||
|
* Fix - NOT_ENABLED_TO_VAULT_PAYMENT_SOURCE on PayPal transactions when using ACDC Vaulting without PayPal Vault approval #2955
|
||||||
|
* Fix - Express buttons for Free Trial Subscription products on Block Cart/Checkout trigger CANNOT_BE_ZERO_OR_NEGATIVE error #2872
|
||||||
|
* Fix - String translations not applied to Card Fields on Block Checkout #2934
|
||||||
|
* Fix - Fastlane component included in script when Fastlane is disabled #2911
|
||||||
|
* Fix - Zero amount line items may trigger CANNOT_BE_ZERO_OR_NEGATIVE error after rounding error #2906
|
||||||
|
* Fix - “Save changes” is grey and unclickable when switching from Sandbox to Live #2895
|
||||||
|
* Fix - plugin queries variations when button/messaging is disabled on single product page #2896
|
||||||
|
* Fix - Use get_id instead of get_order_number on setting custom_id (author @0verscore) #2930
|
||||||
|
* Enhancement - Improve fraud response order notes for Advanced Card Processing transactions #2905
|
||||||
|
* Tweak - Update the minimum plugin requirements to WordPress 6.5 & WooCommerce 9.2 #2920
|
||||||
|
|
||||||
= 2.9.5 - 2024-12-10 =
|
= 2.9.5 - 2024-12-10 =
|
||||||
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
* Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
||||||
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
* Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
||||||
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
* Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
||||||
Fix - "Voide authorization" button does not appear for Apple Pay/Google Pay orders when payment buttons are separated #2752
|
* Fix - "Voide authorization" button does not appear for Apple Pay/Google Pay orders when payment buttons are separated #2752
|
||||||
Fix - Additional payment tokens saved with new customer_id #2820
|
* Fix - Additional payment tokens saved with new customer_id #2820
|
||||||
Fix - Vaulted payment method may not be displayed in PayPal button for return buyer #2809
|
* Fix - Vaulted payment method may not be displayed in PayPal button for return buyer #2809
|
||||||
Fix - Conflict with EasyShip plugin due to shipping methods loading too early #2845
|
* Fix - Conflict with EasyShip plugin due to shipping methods loading too early #2845
|
||||||
Fix - Restore accidentally removed ACDC currencies #2838
|
* Fix - Restore accidentally removed ACDC currencies #2838
|
||||||
Enhancement - Native gateway icon for PayPal & Pay upon Invoice gateways #2712
|
* Enhancement - Native gateway icon for PayPal & Pay upon Invoice gateways #2712
|
||||||
Enhancement - Allow disabling specific card types for Fastlane #2704
|
* Enhancement - Allow disabling specific card types for Fastlane #2704
|
||||||
Enhancement - Fastlane Insights SDK implementation for block Checkout #2737
|
* Enhancement - Fastlane Insights SDK implementation for block Checkout #2737
|
||||||
Enhancement - Hide split local APMs in Payments settings tab when PayPal is not enabled #2703
|
* Enhancement - Hide split local APMs in Payments settings tab when PayPal is not enabled #2703
|
||||||
Enhancement - Do not load split local APMs on Checkout when PayPal is not enabled #2792
|
* Enhancement - Do not load split local APMs on Checkout when PayPal is not enabled #2792
|
||||||
Enhancement - Add support for Button Options in the Block Checkout for Apple Pay & Google Pay buttons #2797 #2772
|
* Enhancement - Add support for Button Options in the Block Checkout for Apple Pay & Google Pay buttons #2797 #2772
|
||||||
Enhancement - Disable “Add payment method” button while saving ACDC payment #2794
|
* Enhancement - Disable “Add payment method” button while saving ACDC payment #2794
|
||||||
Enhancement - Sanitize soft_descriptor field #2846 #2854
|
* Enhancement - Sanitize soft_descriptor field #2846 #2854
|
||||||
|
|
||||||
= 2.9.4 - 2024-11-11 =
|
= 2.9.4 - 2024-11-11 =
|
||||||
* Fix - Apple Pay button preview missing in Standard payment and Advanced Processing tabs #2755
|
* Fix - Apple Pay button preview missing in Standard payment and Advanced Processing tabs #2755
|
||||||
|
|
|
@ -85,7 +85,7 @@ class FilePathPluginFactory implements FilePathPluginFactoryInterface {
|
||||||
'Title' => '',
|
'Title' => '',
|
||||||
'Description' => '',
|
'Description' => '',
|
||||||
'TextDomain' => '',
|
'TextDomain' => '',
|
||||||
'RequiresWP' => '6.3',
|
'RequiresWP' => '6.5',
|
||||||
'RequiresPHP' => '7.4',
|
'RequiresPHP' => '7.4',
|
||||||
),
|
),
|
||||||
$plugin_data
|
$plugin_data
|
||||||
|
|
|
@ -3,14 +3,15 @@
|
||||||
* Plugin Name: WooCommerce PayPal Payments
|
* Plugin Name: WooCommerce PayPal Payments
|
||||||
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
||||||
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
||||||
* Version: 2.9.5
|
* Version: 2.9.6
|
||||||
* Author: WooCommerce
|
* Author: WooCommerce
|
||||||
* Author URI: https://woocommerce.com/
|
* Author URI: https://woocommerce.com/
|
||||||
* License: GPL-2.0
|
* License: GPL-2.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Requires Plugins: woocommerce
|
* Requires Plugins: woocommerce
|
||||||
* WC requires at least: 6.9
|
* Requires at least: 6.5
|
||||||
* WC tested up to: 9.4
|
* WC requires at least: 9.2
|
||||||
|
* WC tested up to: 9.5
|
||||||
* Text Domain: woocommerce-paypal-payments
|
* Text Domain: woocommerce-paypal-payments
|
||||||
*
|
*
|
||||||
* @package WooCommerce\PayPalCommerce
|
* @package WooCommerce\PayPalCommerce
|
||||||
|
@ -26,7 +27,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
|
||||||
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
||||||
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
||||||
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
||||||
define( 'PAYPAL_INTEGRATION_DATE', '2024-12-02' );
|
define( 'PAYPAL_INTEGRATION_DATE', '2024-12-31' );
|
||||||
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
||||||
|
|
||||||
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue