| @@ -26,8 +26,9 @@ | ||
| 26 | 26 | |
| 27 | 27 | use SureDonation\Inc\Database\Tables\Donations; |
| 28 | 28 | use SureDonation\Inc\Helper; |
| 29 | 29 | use SureDonation\Inc\Payments\Payment_Helper; |
| 30 | +use SureDonation\Inc\Payments\PayPal\PayPal_Helper; | |
| 30 | 31 | use SureDonation\Inc\Payments\Stripe\Stripe_Helper; |
| 31 | 32 | |
| 32 | 33 | // Exit if accessed directly. |
| 33 | 34 | if ( ! defined( 'ABSPATH' ) ) { |
| @@ -97,8 +98,9 @@ | ||
| 97 | 98 | add_action( 'admin_notices', [ $this, 'display_test_mode_notice' ] ); |
| 98 | 99 | add_action( 'admin_notices', [ $this, 'display_review_notice_gateway' ] ); |
| 99 | 100 | add_action( 'admin_notices', [ $this, 'display_setup_gateway_notice' ] ); |
| 100 | 101 | add_action( 'admin_notices', [ $this, 'display_webhook_notice' ] ); |
| 102 | + add_action( 'admin_notices', [ $this, 'display_paypal_reconnect_notice' ] ); | |
| 101 | 103 | |
| 102 | 104 | // Load the banner-notice styles from the admin <head> (not the late |
| 103 | 105 | // after-markup hook) so the banner never renders unstyled first. |
| 104 | 106 | add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_banner_notice_style' ] ); |
| @@ -399,8 +401,12 @@ | ||
| 399 | 401 | 'configure_gateway' => 'setup_gateway_notice_cta', |
| 400 | 402 | 'maybe_later' => 'setup_gateway_notice_snooze', |
| 401 | 403 | 'dismissed' => 'setup_gateway_notice_dismiss', |
| 402 | 404 | ], |
| 405 | + 'sd-paypal-reconnect' => [ | |
| 406 | + 'paypal_reconnect_notice_cta' => 'paypal_reconnect_notice_cta', | |
| 407 | + 'paypal_reconnect_notice_dismiss' => 'paypal_reconnect_notice_dismiss', | |
| 408 | + ], | |
| 403 | 409 | 'sd-webhook-not-configured' => [ |
| 404 | 410 | 'configure_webhook' => 'webhook_notice_cta', |
| 405 | 411 | 'dismissed' => 'webhook_notice_dismiss', |
| 406 | 412 | ], |
| @@ -488,16 +494,130 @@ | ||
| 488 | 494 | */ |
| 489 | 495 | private function build_setup_notice_markup() { |
| 490 | 496 | return $this->build_banner_notice_markup( |
| 491 | 497 | esc_html__( 'Your donation site is almost ready!', 'suredonation' ), |
| 492 | - esc_html__( 'Connect a payment gateway to start accepting donations. Set up Stripe or PayPal in just a few clicks to go live.', 'suredonation' ), | |
| 498 | + esc_html__( 'Connect Stripe or PayPal and you can start accepting donations today. It takes a few minutes.', 'suredonation' ), | |
| 493 | 499 | Payment_Helper::get_settings_url( 'stripe' ), |
| 494 | - esc_html__( 'Configure Payment Gateway', 'suredonation' ), | |
| 500 | + esc_html__( 'Connect Stripe or PayPal', 'suredonation' ), | |
| 495 | 501 | SUREDONATION_URL . 'images/payment-gateway-notice.png' |
| 496 | 502 | ); |
| 497 | 503 | } |
| 498 | 504 | |
| 499 | 505 | /** |
| 506 | + * PayPal needs reconnecting in live mode notice. | |
| 507 | + * | |
| 508 | + * The partner client id used to be stored without a mode, so on a site that | |
| 509 | + * connected both test and live the last connect overwrote the other's value. | |
| 510 | + * A site left holding the sandbox id renders the sandbox SDK against a live | |
| 511 | + * merchant, PayPal refuses to reconcile that pairing, and no live donation | |
| 512 | + * can be approved at all — silently, since the donor never reaches approval | |
| 513 | + * and nothing is logged. | |
| 514 | + * | |
| 515 | + * The stored value is an opaque string with nothing distinguishing sandbox | |
| 516 | + * from production, so the environment cannot be recovered after the fact. | |
| 517 | + * Reconnecting in live mode writes the mode-specific value and settles it, | |
| 518 | + * which is all this notice asks for. | |
| 519 | + * | |
| 520 | + * Hooked - admin_notices | |
| 521 | + * | |
| 522 | + * @return void | |
| 523 | + * @since 1.5.1 | |
| 524 | + */ | |
| 525 | + public function display_paypal_reconnect_notice() { | |
| 526 | + if ( ! Helper::current_user_can() ) { | |
| 527 | + return; | |
| 528 | + } | |
| 529 | + | |
| 530 | + if ( ! $this->should_show_paypal_reconnect_notice() ) { | |
| 531 | + return; | |
| 532 | + } | |
| 533 | + | |
| 534 | + $this->enqueue_notice_response_script(); | |
| 535 | + ?> | |
| 536 | + <div id="sd-paypal-reconnect" class="notice notice-error is-dismissible"> | |
| 537 | + <p> | |
| 538 | + <?php | |
| 539 | + printf( | |
| 540 | + /* translators: %1$s: link to the PayPal payment settings */ | |
| 541 | + esc_html__( 'PayPal needs reconnecting before it can take live donations on this site. Test and live are separate PayPal connections, and an earlier version stored one of them over the other. Please %1$s while in live mode. It takes a moment and no settings are lost.', 'suredonation' ), | |
| 542 | + sprintf( | |
| 543 | + '<a class="sd-notice-cta" href="%1$s">%2$s</a>', | |
| 544 | + esc_url( Payment_Helper::get_settings_url( 'paypal' ) ), | |
| 545 | + esc_html__( 'reconnect PayPal', 'suredonation' ) | |
| 546 | + ) | |
| 547 | + ); | |
| 548 | + ?> | |
| 549 | + </p> | |
| 550 | + </div> | |
| 551 | + <?php | |
| 552 | + } | |
| 553 | + | |
| 554 | + /** | |
| 555 | + * Whether the PayPal reconnect notice is eligible to show. | |
| 556 | + * | |
| 557 | + * Scoped as tightly as the stored data allows: | |
| 558 | + * | |
| 559 | + * - **Both modes connected.** The only state where the stored value is | |
| 560 | + * ambiguous. A live-only site's value must be the production id, because | |
| 561 | + * nothing else could have written it; a test-only site takes no live | |
| 562 | + * donations either way. | |
| 563 | + * - **The legacy key is still in use for live.** Keyed on the live value | |
| 564 | + * rather than on both being empty: reconnecting test first would otherwise | |
| 565 | + * clear the notice while live — the mode that handles real money — was | |
| 566 | + * still loading a possibly-sandbox id. | |
| 567 | + * - **No completed live PayPal donation.** One is proof the stored value is | |
| 568 | + * the production id, so there is nothing to fix. (A site whose only live | |
| 569 | + * PayPal donation was later refunded no longer counts here and would be | |
| 570 | + * asked to reconnect unnecessarily — one wasted reconnect, against | |
| 571 | + * silently losing every live donation.) | |
| 572 | + * | |
| 573 | + * A site that connected test and then live, so its last connect wrote the | |
| 574 | + * correct value, and which has not taken a live PayPal donation yet, is | |
| 575 | + * asked to reconnect when it does not need to. That case cannot be told | |
| 576 | + * apart from a broken one — which is exactly why the mode is not inferred. | |
| 577 | + * | |
| 578 | + * @return bool | |
| 579 | + * @since 1.5.1 | |
| 580 | + */ | |
| 581 | + private function should_show_paypal_reconnect_notice() { | |
| 582 | + if ( ! PayPal_Helper::is_paypal_connected( 'live' ) || ! PayPal_Helper::is_paypal_connected( 'test' ) ) { | |
| 583 | + return false; | |
| 584 | + } | |
| 585 | + | |
| 586 | + $settings = PayPal_Helper::get_all_paypal_settings(); | |
| 587 | + | |
| 588 | + $legacy_client_id = isset( $settings['partner_client_id'] ) && is_string( $settings['partner_client_id'] ) | |
| 589 | + ? $settings['partner_client_id'] | |
| 590 | + : ''; | |
| 591 | + | |
| 592 | + if ( '' === $legacy_client_id ) { | |
| 593 | + return false; | |
| 594 | + } | |
| 595 | + | |
| 596 | + $live_client_id = isset( $settings['partner_client_id_live'] ) && is_string( $settings['partner_client_id_live'] ) | |
| 597 | + ? $settings['partner_client_id_live'] | |
| 598 | + : ''; | |
| 599 | + | |
| 600 | + if ( '' !== $live_client_id ) { | |
| 601 | + return false; | |
| 602 | + } | |
| 603 | + | |
| 604 | + // Cached like has_live_donation(): once a live PayPal donation exists the | |
| 605 | + // answer can never go back, so the count runs at most once per site. | |
| 606 | + if ( get_option( 'suredonation_has_live_paypal_donation' ) ) { | |
| 607 | + return false; | |
| 608 | + } | |
| 609 | + | |
| 610 | + if ( Donations::count_live_completed( 'paypal' ) >= 1 ) { | |
| 611 | + update_option( 'suredonation_has_live_paypal_donation', 1, false ); | |
| 612 | + | |
| 613 | + return false; | |
| 614 | + } | |
| 615 | + | |
| 616 | + return true; | |
| 617 | + } | |
| 618 | + | |
| 619 | + /** | |
| 500 | 620 | * Build the markup for the "switch to live mode" test-mode notice |
| 501 | 621 | * (notice A2). |
| 502 | 622 | * |
| 503 | 623 | * @return string The notice HTML markup. |
| @@ -503,11 +623,17 @@ | ||
| 503 | 623 | * @return string The notice HTML markup. |
| 504 | 624 | * @since 1.3.0 |
| 505 | 625 | */ |
| 506 | 626 | private function build_test_mode_notice_markup() { |
| 627 | + // The PayPal sandbox requirement is deliberately not here. It is gateway | |
| 628 | + // advice rather than something about the site being in test mode, and it | |
| 629 | + // belongs beside the mode control in payment settings, where a merchant | |
| 630 | + // is choosing the mode rather than being told about it. | |
| 631 | + $text = esc_html__( 'Supporters cannot donate while your site is in test mode. Anything they try now is a test and no money reaches you. Switch to live mode when you are ready to accept real donations.', 'suredonation' ); | |
| 632 | + | |
| 507 | 633 | return $this->build_banner_notice_markup( |
| 508 | 634 | esc_html__( 'SureDonation is in test mode', 'suredonation' ), |
| 509 | - esc_html__( 'No real payments are being accepted right now. Switch to live mode to start collecting real donations.', 'suredonation' ), | |
| 635 | + $text, | |
| 510 | 636 | Payment_Helper::get_settings_url(), |
| 511 | 637 | esc_html__( 'Switch to Live Mode', 'suredonation' ) |
| 512 | 638 | ); |
| 513 | 639 | } |