diff --git a/api/order-functions.php b/api/order-functions.php
index 340be8585..cbfa42968 100644
--- a/api/order-functions.php
+++ b/api/order-functions.php
@@ -87,6 +87,32 @@ function ppcp_capture_order( WC_Order $wc_order ): void {
}
}
+/**
+ * Reauthorizes the PayPal order.
+ *
+ * @param WC_Order $wc_order The WC order.
+ * @throws InvalidArgumentException When the order cannot be captured.
+ * @throws Exception When the operation fails.
+ */
+function ppcp_reauthorize_order( WC_Order $wc_order ): void {
+ $intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) );
+
+ if ( $intent !== 'AUTHORIZE' ) {
+ throw new InvalidArgumentException( 'Only orders with "authorize" intent can be reauthorized.' );
+ }
+ $captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) );
+ if ( $captured ) {
+ throw new InvalidArgumentException( 'The order is already captured.' );
+ }
+
+ $authorized_payment_processor = PPCP::container()->get( 'wcgateway.processor.authorized-payments' );
+ assert( $authorized_payment_processor instanceof AuthorizedPaymentsProcessor );
+
+ if ( $authorized_payment_processor->reauthorize_payment( $wc_order ) !== AuthorizedPaymentsProcessor::SUCCESSFUL ) {
+ throw new RuntimeException( $authorized_payment_processor->reauthorization_failure_reason() ?: 'Reauthorization failed.' );
+ }
+}
+
/**
* Refunds the PayPal order.
* Note that you can use wc_refund_payment() to trigger the refund in WC and PayPal.
diff --git a/changelog.txt b/changelog.txt
index 86251c280..ad4695527 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,40 @@
*** Changelog ***
+= 2.6.1 - 2024-04-09 =
+* Fix - Payment tokens fixes and adjustments #2106
+* Fix - Pay upon Invoice: Add input validation to Experience Context fields #2092
+* Fix - Disable markup in get_plugin_data() returns to fix an issue with wptexturize() #2094
+* Fix - Problem changing the shipping option in block pages #2142
+* Fix - Saved payment token deleted after payment with another saved payment token #2146
+* Enhancement - Pay later messaging configurator improvements #2107
+* Enhancement - Replace the middleware URL from connect.woocommerce.com to api.woocommerce.com/integrations #2130
+* Enhancement - Remove all Sofort references as it has been deprecated #2124
+* Enhancement - Improve funding source names #2118
+* Enhancement - More fraud prevention capabilities by storing additional data in the order #2125
+* Enhancement - Update ACDC currency eligibility for AMEX #2129
+* Enhancement - Sync shipping options with Venmo when skipping final confirmation on Checkout #2108
+* Enhancement - Card Fields: Add a filter for the CVC field and update the placeholder to match the label #2089
+* Enhancement - Product Title: Sanitize before sending to PayPal #2090
+* Enhancement - Add filter for disabling permit_multiple_payment_tokens vault attribute #2136
+* Enhancement - Filter to hide PayPal email address not working on order detail #2137
+
+= 2.6.0 - 2024-03-20 =
+* Fix - invoice_id not included in API call when creating payment with saved card #2086
+* Fix - Typo in SCA indicators for ACDC Vault transactions #2083
+* Fix - Payments with saved card tokens use Capture intent when Authorize is configured #2069
+* Fix - WooPayments multi-currency causing currency mismatch error on Block Cart & Checkout pages #2054
+* Fix - "Must pass createSubscription with intent=subscription" error with PayPal Subscriptions mode #2058
+* Fix - "Proceed to PayPal" button displayed for Free trial PayPal Subscription products when payment token is saved #2041
+* Fix - ACDC payments with new credit card may fail when debugging is enabled (JSON malformed by warning) #2051
+* Enhancement - Add Pay Later Messaging block #1897
+* Enhancement - Submit the form instead of refreshing the page to show the save notice #2081
+* Enhancement - Integrate pay later messaging block with the messaging configurator #2080
+* Enhancement - Reauthorize authorized payments #2062
+* Enhancement - Do not handle VAULT.PAYMENT-TOKEN.CREATED webhook for Vault v3 #2079
+* Enhancement - Improve the messaging configurator styles #2053
+* Enhancement - Ensure PayPal Vaulting is not selected as Subscriptions Mode when Reference Transactions are disabled #2057
+* Enhancement - Pay later messaging configurator & messaging block adjustments #2096
+
= 2.5.4 - 2024-02-27 =
* Fix - Cannot enable Apple Pay when API credentials were manually created #2015
* Fix - Cart simulation type error #1943
diff --git a/modules.php b/modules.php
index 8ee91f43e..c6840d8ab 100644
--- a/modules.php
+++ b/modules.php
@@ -68,7 +68,7 @@ return function ( string $root_dir ): iterable {
$modules[] = ( require "$modules_dir/ppcp-save-payment-methods/module.php" )();
}
- if ( PayLaterBlockModule::is_enabled() ) {
+ if ( PayLaterBlockModule::is_module_loading_required() ) {
$modules[] = ( require "$modules_dir/ppcp-paylater-block/module.php" )();
}
diff --git a/modules/ppcp-admin-notices/src/AdminNotices.php b/modules/ppcp-admin-notices/src/AdminNotices.php
index 73fe24b71..0489df02c 100644
--- a/modules/ppcp-admin-notices/src/AdminNotices.php
+++ b/modules/ppcp-admin-notices/src/AdminNotices.php
@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\AdminNotices;
+use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
+use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
@@ -40,6 +42,34 @@ class AdminNotices implements ModuleInterface {
$renderer->render();
}
);
+
+ add_action(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds persisted notices to the notices array.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $notices ) use ( $c ) {
+ if ( ! is_array( $notices ) ) {
+ return $notices;
+ }
+
+ $admin_notices = $c->get( 'admin-notices.repository' );
+ assert( $admin_notices instanceof Repository );
+
+ $persisted_notices = $admin_notices->get_persisted_and_clear();
+
+ if ( $persisted_notices ) {
+ $notices = array_merge( $notices, $persisted_notices );
+ }
+
+ return $notices;
+ }
+ );
}
/**
diff --git a/modules/ppcp-admin-notices/src/Entity/Message.php b/modules/ppcp-admin-notices/src/Entity/Message.php
index 0e8bc959e..97624d872 100644
--- a/modules/ppcp-admin-notices/src/Entity/Message.php
+++ b/modules/ppcp-admin-notices/src/Entity/Message.php
@@ -92,4 +92,18 @@ class Message {
public function wrapper(): string {
return $this->wrapper;
}
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array(): array {
+ return array(
+ 'type' => $this->type,
+ 'message' => $this->message,
+ 'dismissable' => $this->dismissable,
+ 'wrapper' => $this->wrapper,
+ );
+ }
}
diff --git a/modules/ppcp-admin-notices/src/Repository/Repository.php b/modules/ppcp-admin-notices/src/Repository/Repository.php
index e13a5dd6c..9573a81a4 100644
--- a/modules/ppcp-admin-notices/src/Repository/Repository.php
+++ b/modules/ppcp-admin-notices/src/Repository/Repository.php
@@ -16,7 +16,8 @@ use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
*/
class Repository implements RepositoryInterface {
- const NOTICES_FILTER = 'ppcp.admin-notices.current-notices';
+ const NOTICES_FILTER = 'ppcp.admin-notices.current-notices';
+ const PERSISTED_NOTICES_OPTION = 'woocommerce_ppcp-admin-notices';
/**
* Returns the current messages.
@@ -37,4 +38,40 @@ class Repository implements RepositoryInterface {
}
);
}
+
+ /**
+ * Adds a message to persist between page reloads.
+ *
+ * @param Message $message The message.
+ * @return void
+ */
+ public function persist( Message $message ): void {
+ $persisted_notices = get_option( self::PERSISTED_NOTICES_OPTION ) ?: array();
+
+ $persisted_notices[] = $message->to_array();
+
+ update_option( self::PERSISTED_NOTICES_OPTION, $persisted_notices );
+ }
+
+ /**
+ * Adds a message to persist between page reloads.
+ *
+ * @return array|Message[]
+ */
+ public function get_persisted_and_clear(): array {
+ $notices = array();
+
+ $persisted_data = get_option( self::PERSISTED_NOTICES_OPTION ) ?: array();
+ foreach ( $persisted_data as $notice_data ) {
+ $notices[] = new Message(
+ (string) ( $notice_data['message'] ?? '' ),
+ (string) ( $notice_data['type'] ?? '' ),
+ (bool) ( $notice_data['dismissable'] ?? true ),
+ (string) ( $notice_data['wrapper'] ?? '' )
+ );
+ }
+
+ update_option( self::PERSISTED_NOTICES_OPTION, array(), true );
+ return $notices;
+ }
}
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 0ae8ed7af..225530ec2 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -1408,32 +1408,32 @@ return array(
'BE' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD', 'CAD' ),
+ 'amex' => array(),
),
'BG' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'CY' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'CZ' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'CZK' ),
+ 'amex' => array(),
),
'DE' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'DK' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'DKK' ),
+ 'amex' => array(),
),
'EE' => array(
'mastercard' => array(),
@@ -1443,32 +1443,32 @@ return array(
'ES' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'FI' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'FR' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'GB' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'GBP', 'USD' ),
+ 'amex' => array(),
),
'GR' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'HU' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'HUF' ),
+ 'amex' => array(),
),
'IE' => array(
'mastercard' => array(),
@@ -1478,7 +1478,7 @@ return array(
'IT' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'US' => array(
'mastercard' => array(),
@@ -1489,7 +1489,7 @@ return array(
'CA' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'CAD' ),
+ 'amex' => array( 'CAD', 'USD' ),
'jcb' => array( 'CAD' ),
),
'LI' => array(
@@ -1500,22 +1500,22 @@ return array(
'LT' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'LU' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'LV' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD' ),
+ 'amex' => array(),
),
'MT' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'MX' => array(
'mastercard' => array(),
@@ -1525,7 +1525,7 @@ return array(
'NL' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD' ),
+ 'amex' => array(),
),
'NO' => array(
'mastercard' => array(),
@@ -1535,32 +1535,32 @@ return array(
'PL' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD', 'GBP', 'PLN' ),
+ 'amex' => array(),
),
'PT' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD', 'CAD', 'GBP' ),
+ 'amex' => array(),
),
'RO' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'USD' ),
+ 'amex' => array(),
),
'SE' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'SEK' ),
+ 'amex' => array(),
),
'SI' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR' ),
+ 'amex' => array(),
),
'SK' => array(
'mastercard' => array(),
'visa' => array(),
- 'amex' => array( 'EUR', 'GBP' ),
+ 'amex' => array(),
),
'JP' => array(
'mastercard' => array(),
diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
index ab8d5473b..538ca224d 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php
@@ -115,7 +115,7 @@ class PaymentMethodTokensEndpoint {
* @throws RuntimeException When something when wrong with the request.
* @throws PayPalApiException When something when wrong setting up the token.
*/
- public function payment_tokens( PaymentSource $payment_source ): stdClass {
+ public function create_payment_token( PaymentSource $payment_source ): stdClass {
$data = array(
'payment_source' => array(
$payment_source->name() => $payment_source->properties(),
diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentTokensEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentTokensEndpoint.php
index bfe05d1b5..284bc887f 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentTokensEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentTokensEndpoint.php
@@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WP_Error;
@@ -92,4 +93,53 @@ class PaymentTokensEndpoint {
throw new PayPalApiException( $json, $status_code );
}
}
+
+ /**
+ * Returns all payment tokens for the given customer.
+ *
+ * @param string $customer_id PayPal customer id.
+ * @return array
+ *
+ * @throws RuntimeException When something went wrong with the request.
+ * @throws PayPalApiException When something went wrong getting the payment tokens.
+ */
+ public function payment_tokens_for_customer( string $customer_id ): array {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v3/vault/payment-tokens?customer_id=' . $customer_id;
+ $args = array(
+ 'method' => 'GET',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( $response instanceof WP_Error ) {
+ throw new RuntimeException( $response->get_error_message() );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 200 !== $status_code ) {
+ throw new PayPalApiException( $json, $status_code );
+ }
+
+ $tokens = array();
+ $payment_tokens = $json->payment_tokens ?? array();
+ foreach ( $payment_tokens as $payment_token ) {
+ $name = array_key_first( (array) $payment_token->payment_source ) ?? '';
+ if ( $name ) {
+ $tokens[] = array(
+ 'id' => $payment_token->id,
+ 'payment_source' => new PaymentSource(
+ $name,
+ $payment_token->payment_source->$name
+ ),
+ );
+ }
+ }
+
+ return $tokens;
+ }
}
diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
index 210beae16..484b89a94 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
@@ -193,6 +193,53 @@ class PaymentsEndpoint {
return $this->capture_factory->from_paypal_response( $json );
}
+ /**
+ * Reauthorizes an order.
+ *
+ * @param string $authorization_id The id.
+ * @param Money|null $amount The amount to capture. If not specified, the whole authorized amount is captured.
+ *
+ * @return string
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function reauthorize( string $authorization_id, ?Money $amount = null ) : string {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v2/payments/authorizations/' . $authorization_id . '/reauthorize';
+
+ $data = array();
+ if ( $amount ) {
+ $data['amount'] = $amount->to_array();
+ }
+
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ 'body' => wp_json_encode( $data, JSON_FORCE_OBJECT ),
+ );
+
+ $response = $this->request( $url, $args );
+ $json = json_decode( $response['body'] );
+
+ if ( is_wp_error( $response ) ) {
+ throw new RuntimeException( 'Could not reauthorize authorized payment.' );
+ }
+
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 201 !== $status_code || ! is_object( $json ) ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $json->id;
+ }
+
/**
* Refunds a payment.
*
diff --git a/modules/ppcp-api-client/src/Factory/ItemFactory.php b/modules/ppcp-api-client/src/Factory/ItemFactory.php
index e8fc98c86..c2b14d0e1 100644
--- a/modules/ppcp-api-client/src/Factory/ItemFactory.php
+++ b/modules/ppcp-api-client/src/Factory/ItemFactory.php
@@ -61,10 +61,10 @@ class ItemFactory {
$price = (float) $item['line_subtotal'] / (float) $item['quantity'];
return new Item(
- mb_substr( $product->get_name(), 0, 127 ),
+ $this->prepare_item_string( $product->get_name() ),
new Money( $price, $this->currency ),
$quantity,
- $this->prepare_description( $product->get_description() ),
+ $this->prepare_item_string( $product->get_description() ),
null,
$this->prepare_sku( $product->get_sku() ),
( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
@@ -138,10 +138,10 @@ class ItemFactory {
$image = $product instanceof WC_Product ? wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' ) : '';
return new Item(
- mb_substr( $item->get_name(), 0, 127 ),
+ $this->prepare_item_string( $item->get_name() ),
new Money( $price_without_tax_rounded, $currency ),
$quantity,
- $product instanceof WC_Product ? $this->prepare_description( $product->get_description() ) : '',
+ $product instanceof WC_Product ? $this->prepare_item_string( $product->get_description() ) : '',
null,
$product instanceof WC_Product ? $this->prepare_sku( $product->get_sku() ) : '',
( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
@@ -160,7 +160,7 @@ class ItemFactory {
*/
private function from_wc_order_fee( \WC_Order_Item_Fee $item, \WC_Order $order ): Item {
return new Item(
- $item->get_name(),
+ $this->prepare_item_string( $item->get_name() ),
new Money( (float) $item->get_amount(), $order->get_currency() ),
$item->get_quantity(),
'',
diff --git a/modules/ppcp-api-client/src/Helper/ItemTrait.php b/modules/ppcp-api-client/src/Helper/ItemTrait.php
index 3b1e05bd0..289889290 100644
--- a/modules/ppcp-api-client/src/Helper/ItemTrait.php
+++ b/modules/ppcp-api-client/src/Helper/ItemTrait.php
@@ -12,14 +12,14 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
trait ItemTrait {
/**
- * Cleanups the description and prepares it for sending to PayPal.
+ * Cleans up item strings (title and description for example) and prepares them for sending to PayPal.
*
- * @param string $description Item description.
+ * @param string $string Item string.
* @return string
*/
- protected function prepare_description( string $description ): string {
- $description = strip_shortcodes( wp_strip_all_tags( $description ) );
- return substr( $description, 0, 127 ) ?: '';
+ protected function prepare_item_string( string $string ): string {
+ $string = strip_shortcodes( wp_strip_all_tags( $string ) );
+ return substr( $string, 0, 127 ) ?: '';
}
/**
diff --git a/modules/ppcp-blocks/resources/js/Helper/Helper.js b/modules/ppcp-blocks/resources/js/Helper/Helper.js
new file mode 100644
index 000000000..379c88a49
--- /dev/null
+++ b/modules/ppcp-blocks/resources/js/Helper/Helper.js
@@ -0,0 +1,22 @@
+/**
+ * @param str
+ * @returns {string}
+ */
+export const toSnakeCase = (str) => {
+ return str.replace(/[\w]([A-Z])/g, function(m) {
+ return m[0] + "_" + m[1];
+ }).toLowerCase();
+}
+
+/**
+ * @param obj
+ * @returns {{}}
+ */
+export const convertKeysToSnakeCase = (obj) => {
+ const newObj = {};
+ Object.keys(obj).forEach((key) => {
+ const newKey = toSnakeCase(key);
+ newObj[newKey] = obj[key];
+ });
+ return newObj;
+}
diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js
index 5e6450045..dd324bd2c 100644
--- a/modules/ppcp-blocks/resources/js/checkout-block.js
+++ b/modules/ppcp-blocks/resources/js/checkout-block.js
@@ -6,6 +6,9 @@ import {
paypalOrderToWcAddresses,
paypalSubscriptionToWcAddresses
} from "./Helper/Address";
+import {
+ convertKeysToSnakeCase
+} from "./Helper/Helper";
import {
cartHasSubscriptionProducts,
isPayPalSubscription
@@ -18,6 +21,7 @@ import {
} from '../../../ppcp-button/resources/js/modules/Helper/Style'
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
import BlockCheckoutMessagesBootstrap from "./Bootstrap/BlockCheckoutMessagesBootstrap";
+import {keysToCamelCase} from "../../../ppcp-button/resources/js/modules/Helper/Utils";
const config = wc.wcSettings.getSetting('ppcp-gateway_data');
window.ppcpFundingSource = config.fundingSource;
@@ -286,17 +290,43 @@ const PayPalComponent = ({
onClick();
};
- let handleShippingChange = null;
- let handleSubscriptionShippingChange = null;
+ let handleShippingOptionsChange = null;
+ let handleShippingAddressChange = null;
+ let handleSubscriptionShippingOptionsChange = null;
+ let handleSubscriptionShippingAddressChange = null;
if (shippingData.needsShipping && !config.finalReviewEnabled) {
- handleShippingChange = async (data, actions) => {
+ handleShippingOptionsChange = async (data, actions) => {
try {
- const shippingOptionId = data.selected_shipping_option?.id;
+ const shippingOptionId = data.selectedShippingOption?.id;
if (shippingOptionId) {
+ await wp.data.dispatch('wc/store/cart').selectShippingRate(shippingOptionId);
await shippingData.setSelectedRates(shippingOptionId);
}
- const address = paypalAddressToWc(data.shipping_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();
+ }
+ };
+
+ handleShippingAddressChange = async (data, actions) => {
+ try {
+ const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
await wp.data.dispatch('wc/store/cart').updateCustomerData({
shipping_address: address,
@@ -325,14 +355,23 @@ const PayPalComponent = ({
}
};
- handleSubscriptionShippingChange = async (data, actions) => {
+ handleSubscriptionShippingOptionsChange = async (data, actions) => {
try {
- const shippingOptionId = data.selected_shipping_option?.id;
+ 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);
- const address = paypalAddressToWc(data.shipping_address);
+ actions.reject();
+ }
+ };
+
+ handleSubscriptionShippingAddressChange = async (data, actions) => {
+ try {
+ const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
await wp.data.dispatch('wc/store/cart').updateCustomerData({
shipping_address: address,
@@ -442,7 +481,8 @@ const PayPalComponent = ({
onError={onClose}
createSubscription={createSubscription}
onApprove={handleApproveSubscription}
- onShippingChange={handleSubscriptionShippingChange}
+ onShippingOptionsChange={handleSubscriptionShippingOptionsChange}
+ onShippingAddressChange={handleSubscriptionShippingAddressChange}
/>
);
}
@@ -456,7 +496,8 @@ const PayPalComponent = ({
onError={onClose}
createOrder={createOrder}
onApprove={handleApprove}
- onShippingChange={handleShippingChange}
+ onShippingOptionsChange={handleShippingOptionsChange}
+ onShippingAddressChange={handleShippingAddressChange}
/>
);
}
diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js
index f766cc6ed..d0e1a8543 100644
--- a/modules/ppcp-button/resources/js/button.js
+++ b/modules/ppcp-button/resources/js/button.js
@@ -137,7 +137,12 @@ const bootstrap = () => {
}
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
- if (isFreeTrial && data.fundingSource !== 'card' && ! PayPalCommerceGateway.subscription_plan_id) {
+ if (
+ isFreeTrial
+ && data.fundingSource !== 'card'
+ && ! PayPalCommerceGateway.subscription_plan_id
+ && ! PayPalCommerceGateway.vault_v3_enabled
+ ) {
freeTrialHandler.handle();
return actions.reject();
}
@@ -241,7 +246,6 @@ document.addEventListener(
if (!typeof (PayPalCommerceGateway)) {
console.error('PayPal button could not be configured.');
return;
- return;
}
if (
diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
index 93ecf7c8f..ed5926816 100644
--- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
@@ -144,6 +144,54 @@ class CheckoutActionHandler {
}
}
}
+
+ addPaymentMethodConfiguration() {
+ return {
+ createVaultSetupToken: async () => {
+ const response = await fetch(this.config.ajax.create_setup_token.endpoint, {
+ method: "POST",
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ nonce: this.config.ajax.create_setup_token.nonce,
+ })
+ });
+
+ const result = await response.json()
+ if (result.data.id) {
+ return result.data.id
+ }
+
+ console.error(result)
+ },
+ onApprove: async ({vaultSetupToken}) => {
+ const response = await fetch(this.config.ajax.create_payment_token_for_guest.endpoint, {
+ method: "POST",
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ nonce: this.config.ajax.create_payment_token_for_guest.nonce,
+ vault_setup_token: vaultSetupToken,
+ })
+ })
+
+ const result = await response.json();
+ if (result.success === true) {
+ document.querySelector('#place_order').click()
+ return;
+ }
+
+ console.error(result)
+ },
+ onError: (error) => {
+ console.error(error)
+ }
+ }
+ }
}
export default CheckoutActionHandler;
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
index e7fae33b0..26a278736 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
@@ -116,6 +116,14 @@ class CheckoutBootstap {
return;
}
+ if(
+ PayPalCommerceGateway.is_free_trial_cart
+ && PayPalCommerceGateway.vault_v3_enabled
+ ) {
+ this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration());
+ return;
+ }
+
this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration());
}
diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php
index a812dc53e..6f781911b 100644
--- a/modules/ppcp-button/services.php
+++ b/modules/ppcp-button/services.php
@@ -145,6 +145,8 @@ return array(
$container->get( 'button.early-wc-checkout-validation-enabled' ),
$container->get( 'button.pay-now-contexts' ),
$container->get( 'wcgateway.funding-sources-without-redirect' ),
+ $container->get( 'vaulting.vault-v3-enabled' ),
+ $container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php
index d63b56af4..d795d4950 100644
--- a/modules/ppcp-button/src/Assets/SmartButton.php
+++ b/modules/ppcp-button/src/Assets/SmartButton.php
@@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Product;
use WC_Product_Variation;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
@@ -33,6 +34,9 @@ use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule;
+use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
+use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
+use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
@@ -184,13 +188,6 @@ class SmartButton implements SmartButtonInterface {
*/
private $funding_sources_without_redirect;
- /**
- * The logger.
- *
- * @var LoggerInterface
- */
- private $logger;
-
/**
* Session handler.
*
@@ -198,6 +195,27 @@ class SmartButton implements SmartButtonInterface {
*/
private $session_handler;
+ /**
+ * Whether Vault v3 module is enabled.
+ *
+ * @var bool
+ */
+ private $vault_v3_enabled;
+
+ /**
+ * Payment tokens endpoint.
+ *
+ * @var PaymentTokensEndpoint
+ */
+ private $payment_tokens_endpoint;
+
+ /**
+ * The logger.
+ *
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* SmartButton constructor.
*
@@ -220,6 +238,8 @@ class SmartButton implements SmartButtonInterface {
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
+ * @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
+ * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@@ -242,6 +262,8 @@ class SmartButton implements SmartButtonInterface {
bool $early_validation_enabled,
array $pay_now_contexts,
array $funding_sources_without_redirect,
+ bool $vault_v3_enabled,
+ PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger
) {
@@ -264,7 +286,9 @@ class SmartButton implements SmartButtonInterface {
$this->early_validation_enabled = $early_validation_enabled;
$this->pay_now_contexts = $pay_now_contexts;
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
+ $this->vault_v3_enabled = $vault_v3_enabled;
$this->logger = $logger;
+ $this->payment_tokens_endpoint = $payment_tokens_endpoint;
}
/**
@@ -631,7 +655,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
$messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $location );
- $has_paylater_block = has_block( 'woocommerce-paypal-payments/paylater-messages' ) && PayLaterBlockModule::is_enabled();
+ $has_paylater_block = has_block( 'woocommerce-paypal-payments/paylater-messages' ) && PayLaterBlockModule::is_block_enabled( $this->settings_status );
switch ( $location ) {
case 'checkout':
@@ -878,7 +902,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
'wrapper' => '#ppcp-messages',
'is_hidden' => ! $this->is_pay_later_filter_enabled_for_location( $this->context() ),
'block' => array(
- 'enabled' => PayLaterBlockModule::is_enabled(),
+ 'enabled' => PayLaterBlockModule::is_block_enabled( $this->settings_status ),
),
'amount' => $amount,
'placement' => $placement,
@@ -990,11 +1014,21 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
if ( $this->settings->has( '3d_secure_contingency' ) ) {
$value = $this->settings->get( '3d_secure_contingency' );
if ( $value ) {
- return $value;
+ return $this->return_3ds_contingency( $value );
}
}
- return 'SCA_WHEN_REQUIRED';
+ return $this->return_3ds_contingency( 'SCA_WHEN_REQUIRED' );
+ }
+
+ /**
+ * Processes and returns the 3D Secure contingency.
+ *
+ * @param string $contingency The ThreeD secure contingency.
+ * @return string
+ */
+ private function return_3ds_contingency( string $contingency ): string {
+ return apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $contingency );
}
/**
@@ -1025,44 +1059,57 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
'redirect' => wc_get_checkout_url(),
'context' => $this->context(),
'ajax' => array(
- 'simulate_cart' => array(
+ 'simulate_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( SimulateCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SimulateCartEndpoint::nonce() ),
),
- 'change_cart' => array(
+ 'change_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
),
- 'create_order' => array(
+ 'create_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ),
),
- 'approve_order' => array(
+ 'approve_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
),
- 'approve_subscription' => array(
+ 'approve_subscription' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveSubscriptionEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveSubscriptionEndpoint::nonce() ),
),
- 'vault_paypal' => array(
+ 'vault_paypal' => array(
'endpoint' => \WC_AJAX::get_endpoint( StartPayPalVaultingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( StartPayPalVaultingEndpoint::nonce() ),
),
- 'save_checkout_form' => array(
+ 'save_checkout_form' => array(
'endpoint' => \WC_AJAX::get_endpoint( SaveCheckoutFormEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SaveCheckoutFormEndpoint::nonce() ),
),
- 'validate_checkout' => array(
+ 'validate_checkout' => array(
'endpoint' => \WC_AJAX::get_endpoint( ValidateCheckoutEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ValidateCheckoutEndpoint::nonce() ),
),
- 'cart_script_params' => array(
+ 'cart_script_params' => array(
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
+ '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() ),
+ ),
+ 'create_payment_token_for_guest' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentTokenForGuest::ENDPOINT ),
+ 'nonce' => wp_create_nonce( CreatePaymentTokenForGuest::nonce() ),
+ ),
),
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
+ 'vault_v3_enabled' => $this->vault_v3_enabled,
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),
'locations_with_subscription_product' => $this->subscription_helper->locations_with_subscription_product(),
@@ -1318,7 +1365,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
- array( 'venmo', 'paylater' )
+ array( 'venmo', 'paylater', 'paypal' )
)
);
}
@@ -1339,6 +1386,20 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
$disable_funding[] = 'paylater';
}
+ $disable_funding = array_filter(
+ $disable_funding,
+ /**
+ * Make sure paypal is not sent in disable funding.
+ *
+ * @param string $funding_source The funding_source.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $funding_source ) {
+ return $funding_source !== 'paypal';
+ }
+ );
+
if ( count( $disable_funding ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
}
@@ -1880,8 +1941,18 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
*/
private function get_vaulted_paypal_email(): string {
try {
- $tokens = $this->get_payment_tokens();
+ $customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
+ if ( $customer_id ) {
+ $customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id );
+ foreach ( $customer_tokens as $token ) {
+ $email_address = $token['payment_source']->properties()->email_address ?? '';
+ if ( $email_address ) {
+ return $email_address;
+ }
+ }
+ }
+ $tokens = $this->get_payment_tokens();
foreach ( $tokens as $token ) {
if ( isset( $token->source()->paypal ) ) {
return $token->source()->paypal->payer->email_address;
@@ -1890,6 +1961,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
} catch ( Exception $exception ) {
$this->logger->error( 'Failed to get PayPal vaulted email. ' . $exception->getMessage() );
}
+
return '';
}
diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
index c286b9f20..1ef06c3be 100644
--- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
@@ -329,6 +329,21 @@ class CreateOrderEndpoint implements EndpointInterface {
if ( 'pay-now' === $data['context'] && is_a( $wc_order, \WC_Order::class ) ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
+
+ $payment_source = $order->payment_source();
+ $payment_source_name = $payment_source ? $payment_source->name() : null;
+ $payer = $order->payer();
+ if (
+ $payer
+ && $payment_source_name
+ && in_array( $payment_source_name, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
+ ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save_meta_data();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
diff --git a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
index 03d8fbeed..46b4ddf41 100644
--- a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
+++ b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
@@ -159,6 +159,22 @@ class EarlyOrderHandler {
$wc_order = wc_get_order( $order_id );
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
+
+ $payment_source = $order->payment_source();
+ $payment_source_name = $payment_source ? $payment_source->name() : null;
+ $payer = $order->payer();
+ if (
+ $payer
+ && $payment_source_name
+ && in_array( $payment_source_name, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
+ && $wc_order instanceof \WC_Order
+ ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save_meta_data();
/**
diff --git a/modules/ppcp-button/src/Helper/ThreeDSecure.php b/modules/ppcp-button/src/Helper/ThreeDSecure.php
index 66c89e4be..1991112b5 100644
--- a/modules/ppcp-button/src/Helper/ThreeDSecure.php
+++ b/modules/ppcp-button/src/Helper/ThreeDSecure.php
@@ -57,21 +57,24 @@ class ThreeDSecure {
*
* @link https://developer.paypal.com/docs/business/checkout/add-capabilities/3d-secure/#authenticationresult
*
- * @param Order $order The order for which the decission is needed.
+ * @param Order $order The order for which the decision is needed.
*
* @return int
*/
public function proceed_with_order( Order $order ): int {
+
+ do_action( 'woocommerce_paypal_payments_three_d_secure_before_check', $order );
+
$payment_source = $order->payment_source();
if ( ! $payment_source ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->brand ?? '' ) ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->authentication_result ?? '' ) ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
$authentication_result = $payment_source->properties()->authentication_result ?? null;
@@ -81,18 +84,31 @@ class ThreeDSecure {
$this->logger->info( '3DS Authentication Result: ' . wc_print_r( $result->to_array(), true ) );
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_POSSIBLE ) {
- return self::PROCCEED;
+ return $this->return_decision( self::PROCCEED, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_UNKNOWN ) {
- return self::RETRY;
+ return $this->return_decision( self::RETRY, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_NO ) {
- return $this->no_liability_shift( $result );
+ return $this->return_decision( $this->no_liability_shift( $result ), $order );
}
}
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
+ }
+
+ /**
+ * Processes and returns a ThreeD secure decision.
+ *
+ * @param int $decision The ThreeD secure decision.
+ * @param Order $order The PayPal Order object.
+ * @return int
+ */
+ public function return_decision( int $decision, Order $order ) {
+ $decision = apply_filters( 'woocommerce_paypal_payments_three_d_secure_decision', $decision, $order );
+ do_action( 'woocommerce_paypal_payments_three_d_secure_after_check', $order, $decision );
+ return $decision;
}
/**
diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php
index 213134857..6835108c3 100644
--- a/modules/ppcp-card-fields/src/CardFieldsModule.php
+++ b/modules/ppcp-card-fields/src/CardFieldsModule.php
@@ -115,17 +115,19 @@ class CardFieldsModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
+ $three_d_secure_contingency =
+ $settings->has( '3d_secure_contingency' )
+ ? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
+ : '';
+
if (
- $settings->has( '3d_secure_contingency' )
- && (
- $settings->get( '3d_secure_contingency' ) === 'SCA_ALWAYS'
- || $settings->get( '3d_secure_contingency' ) === 'SCA_WHEN_REQUIRED'
- )
+ $three_d_secure_contingency === 'SCA_ALWAYS'
+ || $three_d_secure_contingency === 'SCA_WHEN_REQUIRED'
) {
$data['payment_source']['card'] = array(
'attributes' => array(
'verification' => array(
- 'method' => $settings->get( '3d_secure_contingency' ),
+ 'method' => $three_d_secure_contingency,
),
),
);
diff --git a/modules/ppcp-order-tracking/src/Shipment/Shipment.php b/modules/ppcp-order-tracking/src/Shipment/Shipment.php
index 099801874..9661d574c 100644
--- a/modules/ppcp-order-tracking/src/Shipment/Shipment.php
+++ b/modules/ppcp-order-tracking/src/Shipment/Shipment.php
@@ -169,10 +169,10 @@ class Shipment implements ShipmentInterface {
$image = wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' );
$ppcp_order_item = new Item(
- mb_substr( $item->get_name(), 0, 127 ),
+ $this->prepare_item_string( $item->get_name() ),
new Money( $price_without_tax_rounded, $currency ),
$quantity,
- $this->prepare_description( $product->get_description() ),
+ $this->prepare_item_string( $product->get_description() ),
null,
$this->prepare_sku( $product->get_sku() ),
$product->is_virtual() ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
diff --git a/modules/ppcp-paylater-block/resources/js/edit.js b/modules/ppcp-paylater-block/resources/js/edit.js
index 63330318c..44b4f895d 100644
--- a/modules/ppcp-paylater-block/resources/js/edit.js
+++ b/modules/ppcp-paylater-block/resources/js/edit.js
@@ -7,7 +7,7 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help
import PayPalMessages from "./components/PayPalMessages";
export default function Edit( { attributes, clientId, setAttributes } ) {
- const { layout, logo, position, color, flexColor, flexRatio, placement, id } = attributes;
+ const { layout, logo, position, color, size, flexColor, flexRatio, placement, id } = attributes;
const isFlex = layout === 'flex';
const [paypalScriptState, setPaypalScriptState] = useState(null);
@@ -30,11 +30,12 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
ratio: flexRatio,
text: {
color,
+ size
},
};
let classes = ['ppcp-paylater-block-preview', 'ppcp-overlay-parent'];
- if (PcpPayLaterBlock.vaultingEnabled) {
+ if (PcpPayLaterBlock.vaultingEnabled || !PcpPayLaterBlock.placementEnabled) {
classes = ['ppcp-paylater-block-preview', 'ppcp-paylater-unavailable', 'block-editor-warning'];
}
const props = useBlockProps({className: classes});
@@ -68,6 +69,27 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
}
+ if (!PcpPayLaterBlock.placementEnabled) {
+ return
+
+
{__('PayPal Pay Later Messaging', 'woocommerce-paypal-payments')}
+
{__('Pay Later Messaging cannot be used while the “WooCommerce Block” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', 'woocommerce-paypal-payments')}
+
+
+
+ }
+
let scriptParams = useScriptParams(PcpPayLaterBlock.ajax.cart_script_params);
if (scriptParams === null) {
return loadingElement;
@@ -108,10 +130,10 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
{ !isFlex && ( setAttributes({logo: value})}
@@ -129,24 +151,31 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
{ !isFlex && ( setAttributes({color: value})}
/>)}
+ { !isFlex && ( setAttributes({size: value})}
+ />)}
{ isFlex && ( setAttributes({flexColor: value})}
@@ -154,8 +183,6 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
{ isFlex && ( setAttributes( { placement: value } ) }
diff --git a/modules/ppcp-paylater-block/resources/js/save.js b/modules/ppcp-paylater-block/resources/js/save.js
index 0e221d29c..6a845e7da 100644
--- a/modules/ppcp-paylater-block/resources/js/save.js
+++ b/modules/ppcp-paylater-block/resources/js/save.js
@@ -1,7 +1,7 @@
import { useBlockProps } from '@wordpress/block-editor';
export default function save( { attributes } ) {
- const { layout, logo, position, color, flexColor, flexRatio, placement, id } = attributes;
+ const { layout, logo, position, color, size, flexColor, flexRatio, placement, id } = attributes;
const paypalAttributes = layout === 'flex' ? {
'data-pp-style-layout': 'flex',
'data-pp-style-color': flexColor,
@@ -11,6 +11,7 @@ export default function save( { attributes } ) {
'data-pp-style-logo-type': logo,
'data-pp-style-logo-position': position,
'data-pp-style-text-color': color,
+ 'data-pp-style-text-size': size,
};
if (placement && placement !== 'auto') {
paypalAttributes['data-pp-placement'] = placement;
diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php
index 9544c5686..223ccd972 100644
--- a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php
+++ b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php
@@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@@ -22,16 +23,26 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/
class PayLaterBlockModule implements ModuleInterface {
/**
- * Returns whether the block should be loaded.
+ * Returns whether the block module should be loaded.
*/
- public static function is_enabled(): bool {
+ public static function is_module_loading_required(): bool {
return apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.paylater_block_enabled',
- getenv( 'PCP_PAYLATER_BLOCK' ) === '1'
+ getenv( 'PCP_PAYLATER_BLOCK' ) !== '0'
);
}
+ /**
+ * Returns whether the block is enabled.
+ *
+ * @param SettingsStatus $settings_status The Settings status helper.
+ * @return bool true if the block is enabled, otherwise false.
+ */
+ public static function is_block_enabled( SettingsStatus $settings_status ): bool {
+ return self::is_module_loading_required() && $settings_status->is_pay_later_messaging_enabled_for_location( 'custom_placement' );
+ }
+
/**
* {@inheritDoc}
*/
@@ -71,13 +82,15 @@ class PayLaterBlockModule implements ModuleInterface {
$script_handle,
'PcpPayLaterBlock',
array(
- 'ajax' => array(
+ 'ajax' => array(
'cart_script_params' => array(
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
),
- 'settingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ),
- 'vaultingEnabled' => $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' ),
+ 'settingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ),
+ 'vaultingEnabled' => $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' ),
+ 'placementEnabled' => self::is_block_enabled( $c->get( 'wcgateway.settings.status' ) ),
+ 'payLaterSettingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-pay-later' ),
)
);
diff --git a/modules/ppcp-paylater-configurator/resources/css/paylater-configurator.scss b/modules/ppcp-paylater-configurator/resources/css/paylater-configurator.scss
index 5d7cca405..6b61e8a2c 100644
--- a/modules/ppcp-paylater-configurator/resources/css/paylater-configurator.scss
+++ b/modules/ppcp-paylater-configurator/resources/css/paylater-configurator.scss
@@ -43,6 +43,11 @@
.css-1yo2lxy-text_body_strong, span.css-16jt5za-text_body, span.css-1yo2lxy-text_body_strong, span {
font-size: 14px;
}
+
+ hr {
+ margin-right: 16px;
+ border-top-color: #B1B7BD;
+ }
}
#field-pay_later_messaging_heading h3{
diff --git a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js
index 7250e8ce0..9549ef2af 100644
--- a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js
+++ b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js
@@ -15,13 +15,22 @@ document.addEventListener( 'DOMContentLoaded', () => {
headingRow.parentNode.insertBefore(newRow, headingRow.nextSibling);
- saveChangesButton.addEventListener('click', () => {
- form.querySelector('.' + publishButtonClassName).click();
+ let isSaving = false; // Flag variable to track whether saving is in progress
- // Delay the page refresh by a few milliseconds to ensure changes take effect
- setTimeout(() => {
- location.reload();
- }, 1000);
+ saveChangesButton.addEventListener('click', () => {
+ // Check if saving is not already in progress
+ if (!isSaving) {
+ isSaving = true; // Set flag to indicate saving is in progress
+
+ // Trigger the click event on the publish button
+ form.querySelector('.' + publishButtonClassName).click();
+
+ // Trigger click event on saveChangesButton after a short delay
+ setTimeout(() => {
+ saveChangesButton.click(); // Trigger click event on saveChangesButton
+ isSaving = false; // Reset flag when saving is complete
+ }, 1000); // Adjust the delay as needed
+ }
});
merchantConfigurators.Messaging({
@@ -30,7 +39,7 @@ document.addEventListener( 'DOMContentLoaded', () => {
partnerClientId: PcpPayLaterConfigurator.partnerClientId,
partnerName: 'WooCommerce',
bnCode: 'Woo_PPCP',
- placements: ['cart', 'checkout', 'product', 'category', 'homepage', 'custom_placement'],
+ placements: ['cart', 'checkout', 'product', 'shop', 'home', 'custom_placement'],
styleOverrides: {
button: publishButtonClassName,
header: PcpPayLaterConfigurator.headerClassName,
diff --git a/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php b/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php
index 051a89dd6..df4e4affa 100644
--- a/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php
+++ b/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php
@@ -99,14 +99,15 @@ class SaveConfig {
$this->settings->set( 'pay_later_messaging_enabled', true );
$enabled_locations = array();
-
foreach ( $config as $placement => $data ) {
- $location = $this->configurator_placement_to_location( $placement );
+ $this->save_config_for_location( $data, $placement );
- $this->save_config_for_location( $data, $location );
+ if ( $placement === 'custom_placement' ) {
+ $data = $data[0] ?? array();
+ }
if ( $data['status'] === 'enabled' ) {
- $enabled_locations[] = $location;
+ $enabled_locations[] = $placement;
}
}
@@ -131,6 +132,7 @@ class SaveConfig {
$this->set_value_if_present( $config, 'logo-type', "pay_later_{$location}_message_logo" );
$this->set_value_if_present( $config, 'logo-color', "pay_later_{$location}_message_color" );
$this->set_value_if_present( $config, 'text-size', "pay_later_{$location}_message_text_size" );
+ $this->set_value_if_present( $config, 'text-color', "pay_later_{$location}_message_color" );
}
/**
@@ -145,24 +147,4 @@ class SaveConfig {
$this->settings->set( $settings_key, $config[ $key ] );
}
}
-
- /**
- * Converts the configurator placement into location in the old settings.
- *
- * @param string $placement The configurator placement.
- */
- private function configurator_placement_to_location( string $placement ): string {
- switch ( $placement ) {
- case 'cart':
- case 'checkout':
- case 'product':
- return $placement;
- case 'category':
- return 'shop';
- case 'homepage':
- return 'home';
- default:
- return '';
- }
- }
}
diff --git a/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php b/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php
index cb272984d..c73bbd0ab 100644
--- a/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php
+++ b/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php
@@ -22,11 +22,12 @@ class ConfigFactory {
*/
public function from_settings( Settings $settings ): array {
return array(
- $this->location_to_configurator_placement( 'cart' ) => $this->for_location( $settings, 'cart' ),
- $this->location_to_configurator_placement( 'checkout' ) => $this->for_location( $settings, 'checkout' ),
- $this->location_to_configurator_placement( 'product' ) => $this->for_location( $settings, 'product' ),
- $this->location_to_configurator_placement( 'shop' ) => $this->for_location( $settings, 'shop' ),
- $this->location_to_configurator_placement( 'home' ) => $this->for_location( $settings, 'home' ),
+ 'cart' => $this->for_location( $settings, 'cart' ),
+ 'checkout' => $this->for_location( $settings, 'checkout' ),
+ 'product' => $this->for_location( $settings, 'product' ),
+ 'shop' => $this->for_location( $settings, 'shop' ),
+ 'home' => $this->for_location( $settings, 'home' ),
+ 'custom_placement' => array( $this->for_location( $settings, 'woocommerceBlock' ) ),
);
}
@@ -39,51 +40,88 @@ class ConfigFactory {
private function for_location( Settings $settings, string $location ): array {
$selected_locations = $settings->has( 'pay_later_messaging_locations' ) ? $settings->get( 'pay_later_messaging_locations' ) : array();
- $placement = $this->location_to_configurator_placement( $location );
- if ( in_array( $placement, array( 'category', 'homepage' ), true ) ) {
- $config = array(
- 'layout' => 'flex',
- 'color' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_color", 'black', array( 'black', 'blue', 'white', 'white-no-border' ) ),
- 'ratio' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_ratio", '8x1', array( '8x1', '20x1' ) ),
- );
- } else {
- $config = array(
- 'layout' => 'text',
- 'logo-position' => $this->get_or_default( $settings, "pay_later_{$location}_message_position", 'left' ),
- 'logo-type' => $this->get_or_default( $settings, "pay_later_{$location}_message_logo", 'inline' ),
- 'text-color' => $this->get_or_default( $settings, "pay_later_{$location}_message_color", 'black' ),
- 'text-size' => $this->get_or_default( $settings, "pay_later_{$location}_message_text_size", '12' ),
-
- );
+ switch ( $location ) {
+ case 'shop':
+ case 'home':
+ $config = $this->for_shop_or_home( $settings, $location, $selected_locations );
+ break;
+ case 'woocommerceBlock':
+ $config = $this->for_woocommerce_block( $selected_locations );
+ break;
+ default:
+ $config = $this->for_default_location( $settings, $location, $selected_locations );
+ break;
}
- return array_merge(
- array(
- 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled',
- 'placement' => $placement,
- ),
- $config
+ return $config;
+ }
+
+ /**
+ * Returns the configurator config for shop, home locations.
+ *
+ * @param Settings $settings The settings.
+ * @param string $location The location.
+ * @param string[] $selected_locations The list of selected locations.
+ * @return array{
+ * layout: string,
+ * color: string,
+ * ratio: string,
+ * status: "disabled"|"enabled",
+ * placement: string
+ * } The configurator config map.
+ */
+ private function for_shop_or_home( Settings $settings, string $location, array $selected_locations ): array {
+ return array(
+ 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'flex' ),
+ 'color' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_color", 'black' ),
+ 'ratio' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_ratio", '8x1' ),
+ 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled',
+ 'placement' => $location,
);
}
/**
- * Converts the location name from the old settings into the configurator placement.
+ * Returns the configurator config for woocommerceBlock location.
*
- * @param string $location The location name in the old settings.
+ * @param array $selected_locations The list of selected locations.
+ * @return array{
+ * status: "disabled"|"enabled",
+ * message_reference: string
+ * } The configurator config map.
*/
- private function location_to_configurator_placement( string $location ): string {
- switch ( $location ) {
- case 'cart':
- case 'checkout':
- case 'product':
- return $location;
- case 'shop':
- return 'category';
- case 'home':
- return 'homepage';
- default:
- return '';
- }
+ private function for_woocommerce_block( array $selected_locations ): array {
+ return array(
+ 'status' => in_array( 'custom_placement', $selected_locations, true ) ? 'enabled' : 'disabled',
+ 'message_reference' => 'woocommerceBlock',
+ );
+ }
+
+ /**
+ * Returns the configurator config for default locations.
+ *
+ * @param Settings $settings The settings.
+ * @param string $location The location.
+ * @param string[] $selected_locations The list of selected locations.
+ * @return array{
+ * layout: string,
+ * logo-position: string,
+ * logo-type: string,
+ * text-color: string,
+ * text-size: string,
+ * status: "disabled"|"enabled",
+ * placement: string
+ * } The configurator config map.
+ */
+ private function for_default_location( Settings $settings, string $location, array $selected_locations ): array {
+ return array(
+ 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'text' ),
+ 'logo-position' => $this->get_or_default( $settings, "pay_later_{$location}_message_position", 'left' ),
+ 'logo-type' => $this->get_or_default( $settings, "pay_later_{$location}_message_logo", 'inline' ),
+ 'text-color' => $this->get_or_default( $settings, "pay_later_{$location}_message_color", 'black' ),
+ 'text-size' => $this->get_or_default( $settings, "pay_later_{$location}_message_text_size", '12' ),
+ 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled',
+ 'placement' => $location,
+ );
}
/**
@@ -93,9 +131,9 @@ class ConfigFactory {
* @param string $key The key.
* @param mixed $default The default value.
* @param array|null $allowed_values The list of allowed values, or null if all values are allowed.
- * @return mixed
+ * @return string
*/
- private function get_or_default( Settings $settings, string $key, $default, ?array $allowed_values = null ) {
+ private function get_or_default( Settings $settings, string $key, $default, ?array $allowed_values = null ): string {
if ( $settings->has( $key ) ) {
$value = $settings->get( $key );
if ( ! $allowed_values || in_array( $value, $allowed_values, true ) ) {
diff --git a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php
index d3e110289..fd5d4c6d5 100644
--- a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php
+++ b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php
@@ -29,7 +29,7 @@ class PayLaterConfiguratorModule implements ModuleInterface {
return apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.paylater_configurator_enabled',
- getenv( 'PCP_PAYLATER_CONFIGURATOR' ) === '1'
+ getenv( 'PCP_PAYLATER_CONFIGURATOR' ) !== '0'
);
}
@@ -79,7 +79,7 @@ class PayLaterConfiguratorModule implements ModuleInterface {
static function () use ( $c, $settings ) {
wp_enqueue_script(
'ppcp-paylater-configurator-lib',
- 'https://www.paypalobjects.com/merchant-library/preview/merchant-configurator.js',
+ 'https://www.paypalobjects.com/merchant-library/merchant-configurator.js',
array(),
$c->get( 'ppcp.asset-version' ),
true
diff --git a/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php
index 4d56558ab..2109f13d2 100644
--- a/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php
+++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php
@@ -114,7 +114,7 @@ class SubscriptionsApiHandler {
*/
public function create_product( WC_Product $product ) {
try {
- $subscription_product = $this->products_endpoint->create( $product->get_title(), $this->prepare_description( $product->get_description() ) );
+ $subscription_product = $this->products_endpoint->create( $this->prepare_item_string( $product->get_title() ), $this->prepare_item_string( $product->get_description() ) );
$product->update_meta_data( 'ppcp_subscription_product', $subscription_product->to_array() );
$product->save();
} catch ( RuntimeException $exception ) {
@@ -169,7 +169,7 @@ class SubscriptionsApiHandler {
$catalog_product_name = $catalog_product->name() ?: '';
$catalog_product_description = $catalog_product->description() ?: '';
- $wc_product_description = $this->prepare_description( $product->get_description() ) ?: $product->get_title();
+ $wc_product_description = $this->prepare_item_string( $product->get_description() ) ?: $this->prepare_item_string( $product->get_title() );
if ( $catalog_product_name !== $product->get_title() || $catalog_product_description !== $wc_product_description ) {
$data = array();
diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js
index 8aca64f7c..6178bd92c 100644
--- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js
+++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js
@@ -12,7 +12,7 @@ import ErrorHandler from "../../../ppcp-button/resources/js/modules/ErrorHandler
import {cardFieldStyles} from "../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper";
const errorHandler = new ErrorHandler(
- PayPalCommerceGateway.labels.error.generic,
+ ppcp_add_payment_method.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php
index 5d04b16ab..a0b43f021 100644
--- a/modules/ppcp-save-payment-methods/services.php
+++ b/modules/ppcp-save-payment-methods/services.php
@@ -9,9 +9,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
-use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
+use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Helper\SavePaymentMethodsApplies;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@@ -805,31 +805,17 @@ return array(
$container->get( 'api.endpoint.payment-method-tokens' )
);
},
- 'save-payment-methods.wc-payment-tokens' => static function( ContainerInterface $container ): WooCommercePaymentTokens {
- return new WooCommercePaymentTokens(
- $container->get( 'vaulting.payment-token-helper' ),
- $container->get( 'vaulting.payment-token-factory' ),
- $container->get( 'woocommerce.logger.woocommerce' )
- );
- },
'save-payment-methods.endpoint.create-payment-token' => static function ( ContainerInterface $container ): CreatePaymentToken {
return new CreatePaymentToken(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.payment-method-tokens' ),
- $container->get( 'save-payment-methods.wc-payment-tokens' )
+ $container->get( 'vaulting.wc-payment-tokens' )
);
},
- 'save-payment-methods.endpoint.capture-card-payment' => static function( ContainerInterface $container ): CaptureCardPayment {
- return new CaptureCardPayment(
- $container->get( 'api.host' ),
- $container->get( 'api.bearer' ),
- $container->get( 'api.factory.order' ),
- $container->get( 'api.factory.purchase-unit' ),
- $container->get( 'api.endpoint.order' ),
- $container->get( 'session.handler' ),
- $container->get( 'wc-subscriptions.helpers.real-time-account-updater' ),
- $container->get( 'wcgateway.settings' ),
- $container->get( 'woocommerce.logger.woocommerce' )
+ 'save-payment-methods.endpoint.create-payment-token-for-guest' => static function ( ContainerInterface $container ): CreatePaymentTokenForGuest {
+ return new CreatePaymentTokenForGuest(
+ $container->get( 'button.request-data' ),
+ $container->get( 'api.endpoint.payment-method-tokens' )
);
},
);
diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php
index 3a4807ec2..4fea1f188 100644
--- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php
+++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php
@@ -14,9 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
-use WooCommerce\PayPalCommerce\SavePaymentMethods\WooCommercePaymentTokens;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
+use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
/**
* Class CreatePaymentToken
@@ -96,7 +94,7 @@ class CreatePaymentToken implements EndpointInterface {
)
);
- $result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source );
+ $result = $this->payment_method_tokens_endpoint->create_payment_token( $payment_source );
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
$current_user_id = get_current_user_id();
diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentTokenForGuest.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentTokenForGuest.php
new file mode 100644
index 000000000..e458c96be
--- /dev/null
+++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentTokenForGuest.php
@@ -0,0 +1,90 @@
+request_data = $request_data;
+ $this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ * @throws Exception On Error.
+ */
+ public function handle_request(): bool {
+ $data = $this->request_data->read_request( $this->nonce() );
+
+ /**
+ * Suppress ArgumentTypeCoercion
+ *
+ * @psalm-suppress ArgumentTypeCoercion
+ */
+ $payment_source = new PaymentSource(
+ 'token',
+ (object) array(
+ 'id' => $data['vault_setup_token'],
+ 'type' => 'SETUP_TOKEN',
+ )
+ );
+
+ $result = $this->payment_method_tokens_endpoint->create_payment_token( $payment_source );
+ WC()->session->set( 'ppcp_guest_payment_for_free_trial', $result );
+
+ wp_send_json_success();
+ return true;
+ }
+}
diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
index 7bfb52cee..b667fb10b 100644
--- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
+++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
@@ -19,9 +19,10 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
-use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
+use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
+use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
@@ -141,7 +142,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
- 'permit_multiple_payment_tokens' => true,
+ 'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
),
),
),
@@ -167,7 +168,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
- 'permit_multiple_payment_tokens' => true,
+ 'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
),
),
),
@@ -197,7 +198,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
- $wc_payment_tokens = $c->get( 'save-payment-methods.wc-payment-tokens' );
+ $wc_payment_tokens = $c->get( 'vaulting.wc-payment-tokens' );
assert( $wc_payment_tokens instanceof WooCommercePaymentTokens );
if ( $wc_order->get_payment_method() === CreditCardGateway::ID ) {
@@ -281,7 +282,11 @@ class SavePaymentMethodsModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
- $verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : '';
+
+ $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
@@ -312,6 +317,14 @@ class SavePaymentMethodsModule implements ModuleInterface {
'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 ) {
@@ -359,6 +372,16 @@ class SavePaymentMethodsModule implements ModuleInterface {
}
);
+ 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 );
+
+ $endpoint->handle_request();
+ }
+ );
+
add_action(
'woocommerce_paypal_payments_before_delete_payment_token',
function( string $token_id ) use ( $c ) {
diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php
index 44d042a7e..45355225d 100644
--- a/modules/ppcp-vaulting/services.php
+++ b/modules/ppcp-vaulting/services.php
@@ -56,4 +56,15 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
+ 'vaulting.wc-payment-tokens' => static function( ContainerInterface $container ): WooCommercePaymentTokens {
+ return new WooCommercePaymentTokens(
+ $container->get( 'vaulting.payment-token-helper' ),
+ $container->get( 'vaulting.payment-token-factory' ),
+ $container->get( 'api.endpoint.payment-tokens' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'vaulting.vault-v3-enabled' => static function( ContainerInterface $container ): bool {
+ return $container->has( 'save-payment-methods.eligible' ) && $container->get( 'save-payment-methods.eligible' );
+ },
);
diff --git a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php b/modules/ppcp-vaulting/src/WooCommercePaymentTokens.php
similarity index 70%
rename from modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php
rename to modules/ppcp-vaulting/src/WooCommercePaymentTokens.php
index b0f37a449..9136f7d85 100644
--- a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php
+++ b/modules/ppcp-vaulting/src/WooCommercePaymentTokens.php
@@ -7,18 +7,15 @@
declare(strict_types=1);
-namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
+namespace WooCommerce\PayPalCommerce\Vaulting;
use Exception;
use Psr\Log\LoggerInterface;
use stdClass;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;
-use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay;
-use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
-use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
-use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
-use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@@ -41,6 +38,13 @@ class WooCommercePaymentTokens {
*/
private $payment_token_factory;
+ /**
+ * Payment tokens endpoint.
+ *
+ * @var PaymentTokensEndpoint
+ */
+ private $payment_tokens_endpoint;
+
/**
* The logger.
*
@@ -51,18 +55,21 @@ class WooCommercePaymentTokens {
/**
* WooCommercePaymentTokens constructor.
*
- * @param PaymentTokenHelper $payment_token_helper The payment token helper.
- * @param PaymentTokenFactory $payment_token_factory The payment token factory.
- * @param LoggerInterface $logger The logger.
+ * @param PaymentTokenHelper $payment_token_helper The payment token helper.
+ * @param PaymentTokenFactory $payment_token_factory The payment token factory.
+ * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
+ * @param LoggerInterface $logger The logger.
*/
public function __construct(
PaymentTokenHelper $payment_token_helper,
PaymentTokenFactory $payment_token_factory,
+ PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger
) {
- $this->payment_token_helper = $payment_token_helper;
- $this->payment_token_factory = $payment_token_factory;
- $this->logger = $logger;
+ $this->payment_token_helper = $payment_token_helper;
+ $this->payment_token_factory = $payment_token_factory;
+ $this->payment_tokens_endpoint = $payment_tokens_endpoint;
+ $this->logger = $logger;
}
/**
@@ -80,6 +87,10 @@ class WooCommercePaymentTokens {
string $email
): int {
+ if ( $customer_id === 0 ) {
+ return 0;
+ }
+
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenPayPal::class ) ) {
return 0;
@@ -128,6 +139,10 @@ class WooCommercePaymentTokens {
string $email
): int {
+ if ( $customer_id === 0 ) {
+ return 0;
+ }
+
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenVenmo::class ) ) {
return 0;
@@ -174,6 +189,10 @@ class WooCommercePaymentTokens {
string $token
): int {
+ if ( $customer_id === 0 ) {
+ return 0;
+ }
+
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenApplePay::class ) ) {
return 0;
@@ -212,6 +231,10 @@ class WooCommercePaymentTokens {
* @return int
*/
public function create_payment_token_card( int $customer_id, stdClass $payment_token ): int {
+ if ( $customer_id === 0 ) {
+ return 0;
+ }
+
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, CreditCardGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $payment_token->id ) ) {
return 0;
@@ -219,7 +242,7 @@ class WooCommercePaymentTokens {
$token = new WC_Payment_Token_CC();
$token->set_token( $payment_token->id );
- $token->set_user_id( get_current_user_id() );
+ $token->set_user_id( $customer_id );
$token->set_gateway_id( CreditCardGateway::ID );
$token->set_last4( $payment_token->payment_source->card->last_digits ?? '' );
@@ -243,4 +266,61 @@ class WooCommercePaymentTokens {
$token->save();
return $token->get_id();
}
+
+ /**
+ * Returns PayPal payment tokens for the given WP user id.
+ *
+ * @param int $user_id WP user id.
+ * @return array
+ */
+ public function customer_tokens( int $user_id ): array {
+ $customer_id = get_user_meta( $user_id, '_ppcp_target_customer_id', true );
+ if ( ! $customer_id ) {
+ $customer_id = get_user_meta( $user_id, 'ppcp_customer_id', true );
+ }
+
+ try {
+ $customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id );
+ } catch ( RuntimeException $exception ) {
+ $customer_tokens = array();
+ }
+
+ return $customer_tokens;
+ }
+
+ /**
+ * Creates WC payment tokens for the given WP user id using PayPal payment tokens as source.
+ *
+ * @param array $customer_tokens PayPal customer payment tokens.
+ * @param int $user_id WP user id.
+ * @return void
+ */
+ public function create_wc_tokens( array $customer_tokens, int $user_id ): void {
+ foreach ( $customer_tokens as $customer_token ) {
+ if ( $customer_token['payment_source']->name() === 'paypal' ) {
+ $this->create_payment_token_paypal(
+ $user_id,
+ $customer_token['id'],
+ $customer_token['payment_source']->properties()->email_address ?? ''
+ );
+ }
+
+ if ( $customer_token['payment_source']->name() === 'card' ) {
+ /**
+ * Suppress ArgumentTypeCoercion
+ *
+ * @psalm-suppress ArgumentTypeCoercion
+ */
+ $this->create_payment_token_card(
+ $user_id,
+ (object) array(
+ 'id' => $customer_token['id'],
+ 'payment_source' => (object) array(
+ $customer_token['payment_source']->name() => $customer_token['payment_source']->properties(),
+ ),
+ )
+ );
+ }
+ }
+ }
}
diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php
index 3f20d12dd..6bf3d5ecf 100644
--- a/modules/ppcp-wc-gateway/services.php
+++ b/modules/ppcp-wc-gateway/services.php
@@ -22,6 +22,8 @@ use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
+use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction;
+use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@@ -101,7 +103,10 @@ return array(
$api_shop_country,
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.paypal-checkout-url' ),
- $container->get( 'wcgateway.place-order-button-text' )
+ $container->get( 'wcgateway.place-order-button-text' ),
+ $container->get( 'api.endpoint.payment-tokens' ),
+ $container->get( 'vaulting.vault-v3-enabled' ),
+ $container->get( 'vaulting.wc-payment-tokens' )
);
},
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
@@ -131,8 +136,10 @@ return array(
$vaulted_credit_card_handler,
$container->get( 'onboarding.environment' ),
$container->get( 'api.endpoint.order' ),
- $container->get( 'save-payment-methods.endpoint.capture-card-payment' ),
+ $container->get( 'wcgateway.endpoint.capture-card-payment' ),
$container->get( 'api.prefix' ),
+ $container->get( 'api.endpoint.payment-tokens' ),
+ $container->get( 'vaulting.wc-payment-tokens' ),
$logger
);
},
@@ -384,19 +391,25 @@ return array(
$notice = $container->get( 'wcgateway.notice.authorize-order-action' );
$settings = $container->get( 'wcgateway.settings' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
+ $amount_factory = $container->get( 'api.factory.amount' );
return new AuthorizedPaymentsProcessor(
$order_endpoint,
$payments_endpoint,
$logger,
$notice,
$settings,
- $subscription_helper
+ $subscription_helper,
+ $amount_factory
);
},
'wcgateway.admin.render-authorize-action' => static function ( ContainerInterface $container ): RenderAuthorizeAction {
$column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
return new RenderAuthorizeAction( $column );
},
+ 'wcgateway.admin.render-reauthorize-action' => static function ( ContainerInterface $container ): RenderReauthorizeAction {
+ $column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
+ return new RenderReauthorizeAction( $column );
+ },
'wcgateway.admin.order-payment-status' => static function ( ContainerInterface $container ): PaymentStatusOrderDetail {
$column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
return new PaymentStatusOrderDetail( $column );
@@ -952,10 +965,10 @@ return array(
'ideal' => _x( 'iDEAL', 'Name of payment method', 'woocommerce-paypal-payments' ),
'mybank' => _x( 'MyBank', 'Name of payment method', 'woocommerce-paypal-payments' ),
'p24' => _x( 'Przelewy24', 'Name of payment method', 'woocommerce-paypal-payments' ),
- 'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', 'Name of payment method', 'woocommerce-paypal-payments' ),
'trustly' => _x( 'Trustly', 'Name of payment method', 'woocommerce-paypal-payments' ),
- 'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ 'paylater' => _x( 'PayPal Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ 'paypal' => _x( 'PayPal', 'Name of payment method', 'woocommerce-paypal-payments' ),
);
},
@@ -978,6 +991,7 @@ return array(
array_flip(
array(
'paylater',
+ 'paypal',
)
)
);
@@ -1579,4 +1593,17 @@ return array(
)
);
},
+ 'wcgateway.endpoint.capture-card-payment' => static function( ContainerInterface $container ): CaptureCardPayment {
+ return new CaptureCardPayment(
+ $container->get( 'api.host' ),
+ $container->get( 'api.bearer' ),
+ $container->get( 'api.factory.order' ),
+ $container->get( 'api.factory.purchase-unit' ),
+ $container->get( 'api.endpoint.order' ),
+ $container->get( 'session.handler' ),
+ $container->get( 'wc-subscriptions.helpers.real-time-account-updater' ),
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
);
diff --git a/modules/ppcp-wc-gateway/src/Admin/RenderAuthorizeAction.php b/modules/ppcp-wc-gateway/src/Admin/RenderAuthorizeAction.php
index 1d38e64a6..4185f6125 100644
--- a/modules/ppcp-wc-gateway/src/Admin/RenderAuthorizeAction.php
+++ b/modules/ppcp-wc-gateway/src/Admin/RenderAuthorizeAction.php
@@ -9,9 +9,6 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Admin;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
-
/**
* Class RenderAuthorizeAction
*/
diff --git a/modules/ppcp-wc-gateway/src/Admin/RenderReauthorizeAction.php b/modules/ppcp-wc-gateway/src/Admin/RenderReauthorizeAction.php
new file mode 100644
index 000000000..71dbed0ff
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Admin/RenderReauthorizeAction.php
@@ -0,0 +1,67 @@
+column = $column;
+ }
+
+ /**
+ * Renders the action into the $order_actions array based on the WooCommerce order.
+ *
+ * @param array $order_actions The actions to render into.
+ * @param \WC_Order $wc_order The order for which to render the action.
+ *
+ * @return array
+ */
+ public function render( array $order_actions, \WC_Order $wc_order ) : array {
+
+ if ( ! $this->should_render_for_order( $wc_order ) ) {
+ return $order_actions;
+ }
+
+ $order_actions['ppcp_reauthorize_order'] = esc_html__(
+ 'Reauthorize PayPal payment',
+ 'woocommerce-paypal-payments'
+ );
+ return $order_actions;
+ }
+
+ /**
+ * Whether the action should be rendered for a certain WooCommerce order.
+ *
+ * @param \WC_Order $order The Woocommerce order.
+ *
+ * @return bool
+ */
+ private function should_render_for_order( \WC_Order $order ) : bool {
+ $status = $order->get_status();
+ $not_allowed_statuses = array( 'refunded', 'cancelled', 'failed' );
+ return $this->column->should_render_for_order( $order ) &&
+ ! $this->column->is_captured( $order ) &&
+ ! in_array( $status, $not_allowed_statuses, true );
+ }
+}
diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php b/modules/ppcp-wc-gateway/src/Endpoint/CaptureCardPayment.php
similarity index 98%
rename from modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php
rename to modules/ppcp-wc-gateway/src/Endpoint/CaptureCardPayment.php
index 73df58a70..bfb8b4bc4 100644
--- a/modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php
+++ b/modules/ppcp-wc-gateway/src/Endpoint/CaptureCardPayment.php
@@ -7,7 +7,7 @@
declare(strict_types=1);
-namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint;
+namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
use Psr\Log\LoggerInterface;
use RuntimeException;
diff --git a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
index 071410615..cdfcb3b47 100644
--- a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
+++ b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
@@ -34,7 +34,7 @@ class FundingSourceRenderer {
*
* @var string[]
*/
- protected $own_funding_sources = array( 'venmo', 'paylater' );
+ protected $own_funding_sources = array( 'venmo', 'paylater', 'paypal' );
/**
* FundingSourceRenderer constructor.
@@ -63,7 +63,7 @@ class FundingSourceRenderer {
return $this->funding_sources[ $id ];
}
return sprintf(
- /* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
+ /* translators: %s - BLIK, iDeal, Mercado Pago, etc. */
__( '%s (via PayPal)', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);
@@ -84,7 +84,7 @@ class FundingSourceRenderer {
if ( array_key_exists( $id, $this->funding_sources ) ) {
return sprintf(
- /* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
+ /* translators: %s - BLIK, iDeal, Mercado Pago, etc. */
__( 'Pay via %s.', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);
diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
index f2903d239..6e21e70df 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
@@ -15,30 +15,33 @@ use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
-use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
-use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler;
-use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
-use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
-use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
+use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
+use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
+use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
+use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
+use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
+use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class CreditCardGateway
*/
class CreditCardGateway extends \WC_Payment_Gateway_CC {
- use ProcessPaymentTrait, GatewaySettingsRendererTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait;
+ use ProcessPaymentTrait, GatewaySettingsRendererTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait;
const ID = 'ppcp-credit-card-gateway';
@@ -154,6 +157,20 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/
private $prefix;
+ /**
+ * Payment tokens endpoint.
+ *
+ * @var PaymentTokensEndpoint
+ */
+ private $payment_tokens_endpoint;
+
+ /**
+ * WooCommerce payment tokens factory.
+ *
+ * @var WooCommercePaymentTokens
+ */
+ private $wc_payment_tokens;
+
/**
* The logger.
*
@@ -179,6 +196,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param CaptureCardPayment $capture_card_payment Capture card payment.
* @param string $prefix The prefix.
+ * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
+ * @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@@ -197,6 +216,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
OrderEndpoint $order_endpoint,
CaptureCardPayment $capture_card_payment,
string $prefix,
+ PaymentTokensEndpoint $payment_tokens_endpoint,
+ WooCommercePaymentTokens $wc_payment_tokens,
LoggerInterface $logger
) {
$this->id = self::ID;
@@ -215,6 +236,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
$this->order_endpoint = $order_endpoint;
$this->capture_card_payment = $capture_card_payment;
$this->prefix = $prefix;
+ $this->payment_tokens_endpoint = $payment_tokens_endpoint;
+ $this->wc_payment_tokens = $wc_payment_tokens;
$this->logger = $logger;
if ( $state->current_state() === State::STATE_ONBOARDED ) {
@@ -291,8 +314,10 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/
public function form() {
add_action( 'gettext', array( $this, 'replace_credit_card_cvv_label' ), 10, 3 );
+ add_action( 'gettext', array( $this, 'replace_credit_card_cvv_placeholder' ), 10, 3 );
parent::form();
remove_action( 'gettext', 'replace_credit_card_cvv_label' );
+ remove_action( 'gettext', 'replace_credit_card_cvv_placeholder' );
}
/**
@@ -312,6 +337,23 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
return __( 'CVV', 'woocommerce-paypal-payments' );
}
+ /**
+ * Replace WooCommerce credit card CVV field placeholder.
+ *
+ * @param string $translation Translated text.
+ * @param string $text Original text to translate.
+ * @param string $domain Text domain.
+ *
+ * @return string Translated field.
+ */
+ public function replace_credit_card_cvv_placeholder( string $translation, string $text, string $domain ): string {
+ if ( 'woocommerce' !== $domain || 'CVC' !== $text || ! apply_filters( 'woocommerce_paypal_payments_card_fields_translate_card_cvv', true ) ) {
+ return $translation;
+ }
+
+ return __( 'CVV', 'woocommerce-paypal-payments' );
+ }
+
/**
* Returns the icons of the gateway.
*
@@ -413,10 +455,39 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) );
+
+ if ( $this->is_free_trial_order( $wc_order ) && $card_payment_token_id ) {
+ $customer_tokens = $this->wc_payment_tokens->customer_tokens( get_current_user_id() );
+ foreach ( $customer_tokens as $token ) {
+ if ( $token['payment_source']->name() === 'card' ) {
+ $wc_order->payment_complete();
+ return $this->handle_payment_success( $wc_order );
+ }
+ }
+ }
+
if ( $card_payment_token_id ) {
+ $customer_tokens = $this->wc_payment_tokens->customer_tokens( get_current_user_id() );
+
+ $wc_tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), self::ID );
+
+ if ( $customer_tokens && empty( $wc_tokens ) ) {
+ $this->wc_payment_tokens->create_wc_tokens( $customer_tokens, get_current_user_id() );
+ }
+
+ $customer_token_ids = array();
+ foreach ( $customer_tokens as $customer_token ) {
+ $customer_token_ids[] = $customer_token['id'];
+ }
+
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() );
foreach ( $tokens as $token ) {
if ( $token->get_id() === (int) $card_payment_token_id ) {
+ if ( ! in_array( $token->get_token(), $customer_token_ids, true ) ) {
+ $token->delete();
+ continue;
+ }
+
$custom_id = $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 );
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
index 5c3418d86..6c6568b8d 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
@@ -14,12 +14,14 @@ use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
+use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
@@ -48,12 +50,18 @@ class PayPalGateway extends \WC_Payment_Gateway {
const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
+ const ORDER_PAYER_EMAIL_META_KEY = '_ppcp_paypal_payer_email';
const FEES_META_KEY = '_ppcp_paypal_fees';
const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
const THREE_D_AUTH_RESULT_META_KEY = '_ppcp_paypal_3DS_auth_result';
const FRAUD_RESULT_META_KEY = '_ppcp_paypal_fraud_result';
+ /**
+ * List of payment sources wich we are expected to store the payer email in the WC Order metadata.
+ */
+ const PAYMENT_SOURCES_WITH_PAYER_EMAIL = array( 'paypal', 'paylater', 'venmo' );
+
/**
* The Settings Renderer.
*
@@ -173,26 +181,50 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
private $paypal_checkout_url_factory;
+ /**
+ * Payment tokens endpoint.
+ *
+ * @var PaymentTokensEndpoint
+ */
+ private $payment_tokens_endpoint;
+
+ /**
+ * Whether Vault v3 module is enabled.
+ *
+ * @var bool
+ */
+ private $vault_v3_enabled;
+
+ /**
+ * WooCommerce payment tokens.
+ *
+ * @var WooCommercePaymentTokens
+ */
+ private $wc_payment_tokens;
+
/**
* PayPalGateway constructor.
*
- * @param SettingsRenderer $settings_renderer The Settings Renderer.
- * @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
- * @param OrderProcessor $order_processor The Order Processor.
- * @param ContainerInterface $config The settings.
- * @param SessionHandler $session_handler The Session Handler.
- * @param RefundProcessor $refund_processor The Refund Processor.
- * @param State $state The state.
- * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
- * @param SubscriptionHelper $subscription_helper The subscription helper.
- * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
- * @param Environment $environment The environment.
- * @param PaymentTokenRepository $payment_token_repository The payment token repository.
- * @param LoggerInterface $logger The logger.
- * @param string $api_shop_country The api shop country.
- * @param OrderEndpoint $order_endpoint The order endpoint.
- * @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID.
- * @param string $place_order_button_text The text for the standard "Place order" button.
+ * @param SettingsRenderer $settings_renderer The Settings Renderer.
+ * @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
+ * @param OrderProcessor $order_processor The Order Processor.
+ * @param ContainerInterface $config The settings.
+ * @param SessionHandler $session_handler The Session Handler.
+ * @param RefundProcessor $refund_processor The Refund Processor.
+ * @param State $state The state.
+ * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
+ * @param SubscriptionHelper $subscription_helper The subscription helper.
+ * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
+ * @param Environment $environment The environment.
+ * @param PaymentTokenRepository $payment_token_repository The payment token repository.
+ * @param LoggerInterface $logger The logger.
+ * @param string $api_shop_country The api shop country.
+ * @param OrderEndpoint $order_endpoint The order endpoint.
+ * @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID.
+ * @param string $place_order_button_text The text for the standard "Place order" button.
+ * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
+ * @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
+ * @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@@ -211,7 +243,10 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $api_shop_country,
OrderEndpoint $order_endpoint,
callable $paypal_checkout_url_factory,
- string $place_order_button_text
+ string $place_order_button_text,
+ PaymentTokensEndpoint $payment_tokens_endpoint,
+ bool $vault_v3_enabled,
+ WooCommercePaymentTokens $wc_payment_tokens
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
@@ -231,6 +266,10 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->api_shop_country = $api_shop_country;
$this->paypal_checkout_url_factory = $paypal_checkout_url_factory;
$this->order_button_text = $place_order_button_text;
+ $this->order_endpoint = $order_endpoint;
+ $this->payment_tokens_endpoint = $payment_tokens_endpoint;
+ $this->vault_v3_enabled = $vault_v3_enabled;
+ $this->wc_payment_tokens = $wc_payment_tokens;
if ( $this->onboarded ) {
$this->supports = array( 'refunds', 'tokenization' );
@@ -295,8 +334,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
'process_admin_options',
)
);
-
- $this->order_endpoint = $order_endpoint;
}
/**
@@ -496,7 +533,49 @@ class PayPalGateway extends \WC_Payment_Gateway {
$wc_order->save();
}
- if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) && ! $this->subscription_helper->paypal_subscription_id() ) {
+ if (
+ 'card' !== $funding_source
+ && $this->is_free_trial_order( $wc_order )
+ && ! $this->subscription_helper->paypal_subscription_id()
+ ) {
+ $ppcp_guest_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null;
+ if ( $this->vault_v3_enabled && $ppcp_guest_payment_for_free_trial ) {
+ $customer_id = $ppcp_guest_payment_for_free_trial->customer->id ?? '';
+ if ( $customer_id ) {
+ update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
+ }
+
+ if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal ) ) {
+ $email = '';
+ if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address ) ) {
+ $email = $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address;
+ }
+
+ $this->wc_payment_tokens->create_payment_token_paypal(
+ $wc_order->get_customer_id(),
+ $ppcp_guest_payment_for_free_trial->id,
+ $email
+ );
+ }
+
+ WC()->session->set( 'ppcp_guest_payment_for_free_trial', null );
+
+ $wc_order->payment_complete();
+ return $this->handle_payment_success( $wc_order );
+ }
+
+ $customer_id = get_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', true );
+ if ( $customer_id ) {
+ $customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id );
+ foreach ( $customer_tokens as $token ) {
+ $payment_source_name = $token['payment_source']->name() ?? '';
+ if ( $payment_source_name === 'paypal' || $payment_source_name === 'venmo' ) {
+ $wc_order->payment_complete();
+ return $this->handle_payment_success( $wc_order );
+ }
+ }
+ }
+
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
@@ -509,7 +588,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
$wc_order->payment_complete();
-
return $this->handle_payment_success( $wc_order );
}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php
index 20ab81fb6..d4ca29121 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php
@@ -198,25 +198,41 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway {
'description' => __( "Specify brand name, logo and customer service instructions to be presented on Ratepay's payment instructions.", 'woocommerce-paypal-payments' ),
),
'brand_name' => array(
- 'title' => __( 'Brand name', 'woocommerce-paypal-payments' ),
- 'type' => 'text',
- 'default' => get_bloginfo( 'name' ) ?? '',
- 'desc_tip' => true,
- 'description' => __( 'Merchant name displayed in Ratepay\'s payment instructions.', 'woocommerce-paypal-payments' ),
+ 'title' => __( 'Brand name', 'woocommerce-paypal-payments' ),
+ 'type' => 'text',
+ 'default' => get_bloginfo( 'name' ) ?? '',
+ 'desc_tip' => true,
+ 'description' => __( 'Merchant name displayed in Ratepay\'s payment instructions. Should not exceed 127 characters.', 'woocommerce-paypal-payments' ),
+ 'maxlength' => 127,
+ 'custom_attributes' => array(
+ 'pattern' => '.{1,127}',
+ 'autocomplete' => 'off',
+ 'required' => '',
+ ),
),
'logo_url' => array(
- 'title' => __( 'Logo URL', 'woocommerce-paypal-payments' ),
- 'type' => 'url',
- 'default' => '',
- 'desc_tip' => true,
- 'description' => __( 'Logo to be presented on Ratepay\'s payment instructions.', 'woocommerce-paypal-payments' ),
+ 'title' => __( 'Logo URL', 'woocommerce-paypal-payments' ),
+ 'type' => 'url',
+ 'default' => '',
+ 'desc_tip' => true,
+ 'description' => __( 'Logo to be presented on Ratepay\'s payment instructions.', 'woocommerce-paypal-payments' ),
+ 'custom_attributes' => array(
+ 'pattern' => '.+',
+ 'autocomplete' => 'off',
+ 'required' => '',
+ ),
),
'customer_service_instructions' => array(
- 'title' => __( 'Customer service instructions', 'woocommerce-paypal-payments' ),
- 'type' => 'text',
- 'default' => '',
- 'desc_tip' => true,
- 'description' => __( 'Customer service instructions to be presented on Ratepay\'s payment instructions.', 'woocommerce-paypal-payments' ),
+ 'title' => __( 'Customer service instructions', 'woocommerce-paypal-payments' ),
+ 'type' => 'text',
+ 'default' => '',
+ 'desc_tip' => true,
+ 'description' => __( 'Customer service instructions to be presented on Ratepay\'s payment instructions.', 'woocommerce-paypal-payments' ),
+ 'custom_attributes' => array(
+ 'pattern' => '.+',
+ 'autocomplete' => 'off',
+ 'required' => '',
+ ),
),
);
}
diff --git a/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php b/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php
index e84a80b92..34bc13060 100644
--- a/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php
+++ b/modules/ppcp-wc-gateway/src/Processor/AuthorizedPaymentsProcessor.php
@@ -10,6 +10,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Exception;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\AmountFactory;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use WC_Order;
@@ -91,6 +93,20 @@ class AuthorizedPaymentsProcessor {
*/
private $subscription_helper;
+ /**
+ * The amount factory.
+ *
+ * @var AmountFactory
+ */
+ private $amount_factory;
+
+ /**
+ * The reauthorization failure reason.
+ *
+ * @var string
+ */
+ private $reauthorization_failure_reason = '';
+
/**
* AuthorizedPaymentsProcessor constructor.
*
@@ -100,6 +116,7 @@ class AuthorizedPaymentsProcessor {
* @param AuthorizeOrderActionNotice $notice The notice.
* @param ContainerInterface $config The settings.
* @param SubscriptionHelper $subscription_helper The subscription helper.
+ * @param AmountFactory $amount_factory The amount factory.
*/
public function __construct(
OrderEndpoint $order_endpoint,
@@ -107,7 +124,8 @@ class AuthorizedPaymentsProcessor {
LoggerInterface $logger,
AuthorizeOrderActionNotice $notice,
ContainerInterface $config,
- SubscriptionHelper $subscription_helper
+ SubscriptionHelper $subscription_helper,
+ AmountFactory $amount_factory
) {
$this->order_endpoint = $order_endpoint;
@@ -116,6 +134,7 @@ class AuthorizedPaymentsProcessor {
$this->notice = $notice;
$this->config = $config;
$this->subscription_helper = $subscription_helper;
+ $this->amount_factory = $amount_factory;
}
/**
@@ -249,6 +268,67 @@ class AuthorizedPaymentsProcessor {
}
}
+ /**
+ * Reauthorizes an authorized payment for an WooCommerce order.
+ *
+ * @param WC_Order $wc_order The WooCommerce order.
+ *
+ * @return string The status or reauthorization id.
+ */
+ public function reauthorize_payment( WC_Order $wc_order ): string {
+ $this->reauthorization_failure_reason = '';
+
+ try {
+ $order = $this->paypal_order_from_wc_order( $wc_order );
+ } catch ( Exception $exception ) {
+ $this->logger->error( 'Could not get PayPal order from WC order: ' . $exception->getMessage() );
+ if ( $exception->getCode() === 404 ) {
+ return self::NOT_FOUND;
+ }
+ return self::INACCESSIBLE;
+ }
+
+ $amount = $this->amount_factory->from_wc_order( $wc_order );
+
+ $authorizations = $this->all_authorizations( $order );
+ $uncaptured_authorizations = $this->authorizations_to_capture( ...$authorizations );
+
+ if ( ! $uncaptured_authorizations ) {
+ if ( $this->captured_authorizations( ...$authorizations ) ) {
+ $this->logger->info( 'Authorizations already captured.' );
+ return self::ALREADY_CAPTURED;
+ }
+
+ $this->logger->info( 'Bad authorization.' );
+ return self::BAD_AUTHORIZATION;
+ }
+
+ $authorization = end( $uncaptured_authorizations );
+
+ try {
+ $this->payments_endpoint->reauthorize( $authorization->id(), new Money( $amount->value(), $amount->currency_code() ) );
+ } catch ( PayPalApiException $exception ) {
+ $this->reauthorization_failure_reason = $exception->details()[0]->description ?? null;
+ $this->logger->error( 'Reauthorization failed: ' . $exception->name() . ' | ' . $this->reauthorization_failure_reason );
+ return self::FAILED;
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( 'Failed to capture authorization: ' . $exception->getMessage() );
+ return self::FAILED;
+ }
+
+ return self::SUCCESSFUL;
+ }
+
+ /**
+ * The reason for a failed reauthorization.
+ *
+ * @return string
+ */
+ public function reauthorization_failure_reason(): string {
+ return $this->reauthorization_failure_reason;
+ }
+
/**
* Voids authorizations for the given PayPal order.
*
@@ -392,4 +472,5 @@ class AuthorizedPaymentsProcessor {
}
);
}
+
}
diff --git a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
index 32569c60b..51a3a741c 100644
--- a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
@@ -76,7 +76,7 @@ trait CreditCardOrderInfoHandlingTrait {
/**
* Fired when the 3DS information is added to WC order.
*/
- do_action( 'woocommerce_paypal_payments_thee_d_secure_added', $wc_order, $order );
+ do_action( 'woocommerce_paypal_payments_three_d_secure_added', $wc_order, $order );
}
}
@@ -96,8 +96,9 @@ trait CreditCardOrderInfoHandlingTrait {
return;
}
- $fraud_responses = $fraud->to_array();
- $card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
+ $fraud_responses = $fraud->to_array();
+ $card_brand = $payment_source->properties()->brand ?? __( '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' );
/* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
@@ -109,6 +110,7 @@ trait CreditCardOrderInfoHandlingTrait {
%3$s
%4$s
+ %5$s
';
$avs_response_order_note_result = sprintf(
$avs_response_order_note_result_format,
@@ -119,7 +121,9 @@ trait CreditCardOrderInfoHandlingTrait {
/* 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 ) )
+ 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(
$avs_response_order_note_format,
@@ -136,7 +140,13 @@ trait CreditCardOrderInfoHandlingTrait {
);
$wc_order->add_order_note( $cvv_response_order_note );
- $meta_details = array_merge( $fraud_responses, array( 'card_brand' => $card_brand ) );
+ $meta_details = array_merge(
+ $fraud_responses,
+ array(
+ 'card_brand' => $card_brand,
+ 'card_last_digits' => $card_last_digits,
+ )
+ );
$wc_order->update_meta_data( PayPalGateway::FRAUD_RESULT_META_KEY, $meta_details );
$wc_order->save_meta_data();
diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
index b18468d7b..62a80456a 100644
--- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
@@ -45,6 +45,18 @@ trait OrderMetaTrait {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY, $payment_source );
}
+ $payer = $order->payer();
+ if (
+ $payer
+ && $payment_source
+ && in_array( $payment_source, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
+ ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
index 59bb440f1..25a367693 100644
--- a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
+++ b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
@@ -65,7 +65,7 @@ return function ( ContainerInterface $container, array $fields ): array {
-
+