diff --git a/assets/src/js/frontend/give-stripe-elements.js b/assets/src/js/frontend/give-stripe-elements.js index f8ddce85ba..a38a7db83d 100644 --- a/assets/src/js/frontend/give-stripe-elements.js +++ b/assets/src/js/frontend/give-stripe-elements.js @@ -308,67 +308,82 @@ class GiveStripeElements { * @param setupStripeElement * @param cardElements * + * @unreleased Scrolls Stripe checkout modal into view for all screen sizes. + * * @since 2.8.0 */ triggerStripeModal( formElement, stripeElements, setupStripeElement, cardElements ) { - const idPrefixElement = formElement.querySelector( 'input[name="give-form-id-prefix"]' ); - const stripeModalDonateBtn = formElement.querySelector( `#give-stripe-checkout-modal-donate-button-${ idPrefixElement.value }` ); - const cardholderName = formElement.querySelector( 'input[name="card_name"]' ); - const completeCardElements = {}; - let completeCardStatus = false; - - cardElements.forEach( ( cardElement ) => { - completeCardElements.cardName = false; - - cardElement.addEventListener( 'ready', ( e ) => { - completeCardElements[ e.elementType ] = false; - completeCardElements.cardName = 'card' === e.elementType; - } ); - - cardElement.addEventListener( 'change', ( e ) => { - completeCardElements[ e.elementType ] = e.complete; - completeCardStatus = Object.values( completeCardElements ).every( ( string ) => { - return true === string; - } ); - - completeCardStatus ? stripeModalDonateBtn.removeAttribute( 'disabled' ) : stripeModalDonateBtn.setAttribute( 'disabled', 'disabled' ); - } ); - } ); - - if ( null !== cardholderName ) { - cardholderName.addEventListener( 'keyup', ( e ) => { - completeCardElements.cardName = '' !== e.target.value; - completeCardStatus = Object.values( completeCardElements ).every( ( string ) => { - return true === string; - } ); - completeCardStatus ? stripeModalDonateBtn.removeAttribute( 'disabled' ) : stripeModalDonateBtn.setAttribute( 'disabled', 'disabled' ); - } ); - } - - if ( null !== stripeModalDonateBtn ) { - // Process donation on the click of the modal donate button. - stripeModalDonateBtn.addEventListener( 'click', ( e ) => { - const currentModalDonateBtn = e.target; - const loadingAnimationElement = currentModalDonateBtn.nextElementSibling; - const isLegacyForm = stripeModalDonateBtn.getAttribute( 'data-is_legacy_form' ); - - if ( isLegacyForm ) { - currentModalDonateBtn.value = give_global_vars.purchase_loading; - loadingAnimationElement.style.display = 'inline-block'; - } else { - currentModalDonateBtn.value = ''; - loadingAnimationElement.classList.add( 'sequoia-loader' ); - loadingAnimationElement.classList.add( 'spinning' ); - loadingAnimationElement.classList.remove( 'give-loading-animation' ); - } - - // Create Payment Method. - stripeElements.createPaymentMethod( formElement, setupStripeElement, cardElements ); - - e.preventDefault(); - } ); - } - } + const idPrefixElement = formElement.querySelector('input[name="give-form-id-prefix"]'); + const stripeModalDonateBtn = formElement.querySelector( + `#give-stripe-checkout-modal-donate-button-${idPrefixElement.value}` + ); + const cardholderName = formElement.querySelector('input[name="card_name"]'); + const stripeModalContent = document.querySelector('.give-stripe-checkout-modal-container'); + const purchaseButton = document.querySelector('#give-purchase-button'); + const completeCardElements = {}; + let completeCardStatus = false; + + // Scroll checkout modal container into view. + purchaseButton.addEventListener('click', function () { + stripeModalContent.scrollIntoView({behavior: 'smooth'}); + }); + + cardElements.forEach((cardElement) => { + completeCardElements.cardName = false; + + cardElement.addEventListener('ready', (e) => { + completeCardElements[e.elementType] = false; + completeCardElements.cardName = 'card' === e.elementType; + }); + + cardElement.addEventListener('change', (e) => { + completeCardElements[e.elementType] = e.complete; + completeCardStatus = Object.values(completeCardElements).every((string) => { + return true === string; + }); + + completeCardStatus + ? stripeModalDonateBtn.removeAttribute('disabled') + : stripeModalDonateBtn.setAttribute('disabled', 'disabled'); + }); + }); + + if (null !== cardholderName) { + cardholderName.addEventListener('keyup', (e) => { + completeCardElements.cardName = '' !== e.target.value; + completeCardStatus = Object.values(completeCardElements).every((string) => { + return true === string; + }); + completeCardStatus + ? stripeModalDonateBtn.removeAttribute('disabled') + : stripeModalDonateBtn.setAttribute('disabled', 'disabled'); + }); + } + + if (null !== stripeModalDonateBtn) { + // Process donation on the click of the modal donate button. + stripeModalDonateBtn.addEventListener('click', (e) => { + const currentModalDonateBtn = e.target; + const loadingAnimationElement = currentModalDonateBtn.nextElementSibling; + const isLegacyForm = stripeModalDonateBtn.getAttribute('data-is_legacy_form'); + + if (isLegacyForm) { + currentModalDonateBtn.value = give_global_vars.purchase_loading; + loadingAnimationElement.style.display = 'inline-block'; + } else { + currentModalDonateBtn.value = ''; + loadingAnimationElement.classList.add('sequoia-loader'); + loadingAnimationElement.classList.add('spinning'); + loadingAnimationElement.classList.remove('give-loading-animation'); + } + + // Create Payment Method. + stripeElements.createPaymentMethod(formElement, setupStripeElement, cardElements); + + e.preventDefault(); + }); + } + } } export { GiveStripeElements }; diff --git a/blocks/donation-form-grid/edit/inspector.js b/blocks/donation-form-grid/edit/inspector.js index 0381ef0993..9e4bea7896 100644 --- a/blocks/donation-form-grid/edit/inspector.js +++ b/blocks/donation-form-grid/edit/inspector.js @@ -213,7 +213,7 @@ const Inspector = ({attributes, setAttributes}) => { - + select { - line-height: 1.2 !important; - } + > select { + line-height: 1.2 !important; } } +} - .components-form-token-field{ +.give-donation-form-grid--grid-settings { + .components-form-token-field { label { display: none !important;} } @@ -59,4 +58,5 @@ margin-top: 20px !important; border: 1px solid transparent; } +} diff --git a/blocks/donor-wall/edit/inspector.js b/blocks/donor-wall/edit/inspector.js index 8d99ecb761..7567647afb 100644 --- a/blocks/donor-wall/edit/inspector.js +++ b/blocks/donor-wall/edit/inspector.js @@ -225,7 +225,7 @@ const { donorsPerPage, - + select { - line-height: 1.2 !important; - } + > select { + line-height: 1.2 !important; } } +} -.components-form-token-field { - margin-top: -18px !important; - label { display: none !important;} +.give-wall--wall-settings { + .components-form-token-field { + margin-top: -18px !important; + label { display: none !important;} + } } diff --git a/includes/class-notices.php b/includes/class-notices.php index 9cf611167a..bf5979fb71 100644 --- a/includes/class-notices.php +++ b/includes/class-notices.php @@ -285,6 +285,7 @@ public function render_admin_notices() { /** * Render give frontend notices. * + * @unreleased Display registered error on donation form. * @since 1.8.9 * @access public * @@ -295,8 +296,13 @@ public function render_frontend_notices( $form_id = 0 ) { $request_form_id = isset( $_REQUEST['form-id'] ) ? absint( $_REQUEST['form-id'] ) : 0; - // Sanity checks first: Ensure that gateway returned errors display on the appropriate form. - if ( ! isset( $_POST['give_ajax'] ) && $request_form_id !== $form_id ) { + // Sanity checks first: + // - Ensure that gateway returned errors display on the appropriate form. + // - Error should not display on AJAX request. + if ( + isset( $_POST['give_ajax'] ) + || ( $request_form_id && $request_form_id !== $form_id ) + ) { return; } diff --git a/src/Framework/FieldsAPI/Concerns/NameCollision.php b/src/Framework/FieldsAPI/Concerns/NameCollision.php index dccb7bf66d..2fba1d7caa 100644 --- a/src/Framework/FieldsAPI/Concerns/NameCollision.php +++ b/src/Framework/FieldsAPI/Concerns/NameCollision.php @@ -26,14 +26,15 @@ public function checkNameCollisionDeep(Node $node) } /** + * @unreleased add existing and incoming nodes to exception * @since 2.10.2 * * @throws NameCollisionException */ public function checkNameCollision(Node $node) { - if ($this->getNodeByName($node->getName())) { - throw new NameCollisionException($node->getName()); + if ($existingNode = $this->getNodeByName($node->getName())) { + throw new NameCollisionException($node->getName(), $existingNode, $node); } } } diff --git a/src/Framework/FieldsAPI/Exceptions/NameCollisionException.php b/src/Framework/FieldsAPI/Exceptions/NameCollisionException.php index 54d5645518..579b60608d 100644 --- a/src/Framework/FieldsAPI/Exceptions/NameCollisionException.php +++ b/src/Framework/FieldsAPI/Exceptions/NameCollisionException.php @@ -3,15 +3,64 @@ namespace Give\Framework\FieldsAPI\Exceptions; use Give\Framework\Exceptions\Primitives\Exception; +use Give\Framework\FieldsAPI\Contracts\Node; /** + * @unreleased add existing and incoming nodes to exception * @since 2.10.2 */ class NameCollisionException extends Exception { - public function __construct($name, $code = 0, Exception $previous = null) + /** + * @var string + */ + protected $nodeNameCollision; + /** + * @var Node + */ + protected $existingNode; + /** + * @var Node + */ + protected $incomingNode; + + public function __construct( + string $name, + Node $existingNode, + Node $incomingNode, + int $code = 0, + Exception $previous = null + ) { + $this->nodeNameCollision = $name; + $this->existingNode = $existingNode; + $this->incomingNode = $incomingNode; + $message = "Node name collision for $name"; parent::__construct($message, $code, $previous); } + + /** + * @unreleased + */ + public function getNodeNameCollision(): string + { + return $this->nodeNameCollision; + } + + /** + * @unreleased + */ + public function getIncomingNode(): Node + { + return $this->incomingNode; + } + + /** + * @unreleased + */ + public function getExistingNode(): Node + { + return $this->existingNode; + } } diff --git a/src/Framework/PaymentGateways/Traits/HandleHttpResponses.php b/src/Framework/PaymentGateways/Traits/HandleHttpResponses.php index 7d061aa416..36d0846a97 100644 --- a/src/Framework/PaymentGateways/Traits/HandleHttpResponses.php +++ b/src/Framework/PaymentGateways/Traits/HandleHttpResponses.php @@ -11,6 +11,7 @@ trait HandleHttpResponses /** * Handle Response * + * @unreleased added check for responding with json * @since 2.27.0 add support for json content-type * @since 2.18.0 * @@ -19,7 +20,7 @@ trait HandleHttpResponses public function handleResponse($type) { if ($type instanceof RedirectResponse) { - if (isset($_SERVER['CONTENT_TYPE']) && str_contains($_SERVER['CONTENT_TYPE'], "application/json")) { + if ($this->wantsJson()) { wp_send_json([ 'type' => 'redirect', 'data' => [ @@ -60,4 +61,20 @@ public function handleExceptionResponse(\Exception $exception, string $message) give_set_error('PaymentGatewayException', $message); give_send_back_to_checkout(); } + + /** + * This checks the server headers for 'application/json' to determine if it should respond with json. + * + * @unreleased + * + * @return bool + */ + protected function wantsJson(): bool + { + if (isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) { + return true; + } + + return isset($_SERVER['CONTENT_TYPE']) && str_contains($_SERVER['CONTENT_TYPE'], 'application/json'); + } } diff --git a/src/PaymentGateways/PayPalCommerce/ScriptLoader.php b/src/PaymentGateways/PayPalCommerce/ScriptLoader.php index 72f05332ee..0430861564 100644 --- a/src/PaymentGateways/PayPalCommerce/ScriptLoader.php +++ b/src/PaymentGateways/PayPalCommerce/ScriptLoader.php @@ -133,6 +133,7 @@ function givePayPalOnBoardedCallback(mode, authCode, sharedId) { /** * Load public assets. * + * @unreleased Handle exception if client token is not generated. * @since 2.9.0 */ public function loadPublicAssets() @@ -144,6 +145,22 @@ public function loadPublicAssets() /* @var MerchantDetail $merchant */ $merchant = give(MerchantDetail::class); $scriptId = 'give-paypal-commerce-js'; + $clientToken = ''; + + try{ + $clientToken = $this->merchantRepository->getClientToken(); + } catch ( \Exception $exception ) { + give_set_error( + 'give-paypal-commerce-client-token-error', + sprintf( + esc_html__( + 'Unable to load PayPal Commerce client token. Please try again later. Error: %1$s', + 'give' + ), + $exception->getMessage() + ) + ); + } /** * List of PayPal query parameters: https://developer.paypal.com/docs/checkout/reference/customize-sdk/#query-parameters @@ -156,7 +173,7 @@ public function loadPublicAssets() 'disable-funding' => 'credit', 'vault' => true, 'data-partner-attribution-id' => give('PAYPAL_COMMERCE_ATTRIBUTION_ID'), - 'data-client-token' => $this->merchantRepository->getClientToken(), + 'data-client-token' => $clientToken, ]; if (give_is_setting_enabled(give_get_option('paypal_commerce_accept_venmo', 'disabled'))) {