diff --git a/includes/class-newspack.php b/includes/class-newspack.php
index e626ff304e..4376527fe5 100644
--- a/includes/class-newspack.php
+++ b/includes/class-newspack.php
@@ -151,6 +151,7 @@ private function includes() {
// Audience Wizard.
include_once NEWSPACK_ABSPATH . 'includes/wizards/audience/class-audience-wizard.php';
include_once NEWSPACK_ABSPATH . 'includes/wizards/audience/class-audience-campaigns.php';
+ include_once NEWSPACK_ABSPATH . 'includes/wizards/audience/class-audience-donations.php';
// Network Wizard.
include_once NEWSPACK_ABSPATH . 'includes/wizards/class-network-wizard.php';
diff --git a/includes/class-wizards.php b/includes/class-wizards.php
index 3482fd7ad2..b2417dc625 100644
--- a/includes/class-wizards.php
+++ b/includes/class-wizards.php
@@ -56,6 +56,7 @@ public static function init() {
'advertising-sponsors' => new Advertising_Sponsors(),
'audience' => new Audience_Wizard(),
'audience-campaigns' => new Audience_Campaigns(),
+ 'audience-donations' => new Audience_Donations(),
'listings' => new Listings_Wizard(),
'network' => new Network_Wizard(),
'newsletters' => new Newsletters_Wizard(),
diff --git a/includes/wizards/audience/class-audience-donations.php b/includes/wizards/audience/class-audience-donations.php
new file mode 100644
index 0000000000..efe0126540
--- /dev/null
+++ b/includes/wizards/audience/class-audience-donations.php
@@ -0,0 +1,629 @@
+parent_slug,
+ $this->get_name(),
+ esc_html__( 'Donations', 'newspack-plugin' ),
+ $this->capability,
+ $this->slug,
+ [ $this, 'render_wizard' ]
+ );
+ }
+
+ /**
+ * Enqueue scripts and styles.
+ */
+ public function enqueue_scripts_and_styles() {
+ if ( ! $this->is_wizard_page() ) {
+ return;
+ }
+
+ parent::enqueue_scripts_and_styles();
+
+ wp_enqueue_script( 'newspack-wizards' );
+
+ \wp_localize_script(
+ 'newspack-wizards',
+ 'newspackAudienceDonations',
+ [
+ 'emails' => Emails::get_emails( [ Reader_Revenue_Emails::EMAIL_TYPES['RECEIPT'] ], false ),
+ 'email_cpt' => Emails::POST_TYPE,
+ 'salesforce_redirect_url' => Salesforce::get_redirect_url(),
+ 'can_use_name_your_price' => Donations::can_use_name_your_price(),
+ ]
+ );
+ }
+
+ /**
+ * Prevent rendering of Donate block if Reader Revenue platform is set to 'other.
+ *
+ * @param string $block_content The block content about to be rendered.
+ * @param array $block The data of the block about to be rendered.
+ */
+ public static function prevent_rendering_donate_block( $block_content, $block ) {
+ if (
+ isset( $block['blockName'] )
+ && 'newspack-blocks/donate' === $block['blockName']
+ && Donations::is_platform_other()
+ ) {
+ return '';
+ }
+ return $block_content;
+ }
+
+ /**
+ * Register the endpoints needed for the wizard screens.
+ */
+ public function register_api_endpoints() {
+ // Get all data required to render the Wizard.
+ \register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug,
+ [
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'api_fetch' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ ]
+ );
+ // Save basic data about reader revenue platform.
+ \register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug,
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'platform' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_platform' ],
+ ],
+ 'nrh_organization_id' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ 'nrh_custom_domain' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ 'nrh_salesforce_campaign_id' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ 'donor_landing_page' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ ],
+ ]
+ );
+
+ // Save location info.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/location/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_location' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'countrystate' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ 'address1' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ 'address2' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ 'city' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ 'postcode' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ 'currency' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => [ $this, 'api_validate_not_empty' ],
+ ],
+ ],
+ ]
+ );
+
+ // Save Stripe info.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/stripe/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_stripe_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'activate' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'enabled' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'location_code' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ ],
+ ]
+ );
+
+ // Save WooPayments info.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/woopayments/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_woopayments_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'activate' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'enabled' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ ],
+ ]
+ );
+
+ // Save additional settings info.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/settings/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_additional_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'fee_multiplier' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ 'validate_callback' => function ( $value ) {
+ if ( (float) $value > 10 ) {
+ return new WP_Error(
+ 'newspack_invalid_param',
+ __( 'Fee multiplier must be smaller than 10.', 'newspack' )
+ );
+ }
+ return true;
+ },
+ ],
+ 'fee_static' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ 'allow_covering_fees' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'allow_covering_fees_default' => [
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'allow_covering_fees_label' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ 'location_code' => [
+ 'sanitize_callback' => 'Newspack\newspack_clean',
+ ],
+ ],
+ ]
+ );
+
+ // Update Donations settings.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/donations/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_donation_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'amounts' => [
+ 'required' => false,
+ ],
+ 'tiered' => [
+ 'required' => false,
+ 'sanitize_callback' => 'Newspack\newspack_string_to_bool',
+ ],
+ 'disabledFrequencies' => [
+ 'required' => false,
+ ],
+ 'platform' => [
+ 'required' => false,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+
+ // Save Salesforce settings.
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/salesforce/',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'api_update_salesforce_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ 'args' => [
+ 'client_id' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'client_secret' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'access_token' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'refresh_token' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+
+ register_rest_route(
+ NEWSPACK_API_NAMESPACE,
+ '/wizard/' . $this->slug . '/donations/',
+ [
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'api_get_donation_settings' ],
+ 'permission_callback' => [ $this, 'api_permissions_check' ],
+ ]
+ );
+ }
+
+ /**
+ * Get all Wizard Data
+ *
+ * @return WP_REST_Response containing ad units info.
+ */
+ public function api_fetch() {
+ return \rest_ensure_response( $this->fetch_all_data() );
+ }
+
+ /**
+ * Save top-level data.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Boolean success.
+ */
+ public function api_update( $request ) {
+ $params = $request->get_params();
+ Donations::set_platform_slug( $params['platform'] );
+
+ // Update NRH settings.
+ if ( Donations::is_platform_nrh() ) {
+ NRH::update_settings( $params );
+ }
+
+ // Ensure that any Reader Revenue settings changed while the platform wasn't WC are persisted to WC products.
+ if ( Donations::is_platform_wc() ) {
+ Donations::update_donation_product( Donations::get_donation_settings() );
+ }
+
+ return \rest_ensure_response( $this->fetch_all_data() );
+ }
+
+ /**
+ * Save location info.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Boolean success.
+ */
+ public function api_update_location( $request ) {
+ $required_plugins_installed = $this->check_required_plugins_installed();
+ if ( is_wp_error( $required_plugins_installed ) ) {
+ return rest_ensure_response( $required_plugins_installed );
+ }
+ $wc_configuration_manager = Configuration_Managers::configuration_manager_class_for_plugin_slug( 'woocommerce' );
+
+ $params = $request->get_params();
+
+ $defaults = [
+ 'countrystate' => '',
+ 'address1' => '',
+ 'address2' => '',
+ 'city' => '',
+ 'postcode' => '',
+ 'currency' => '',
+ ];
+
+ $args = wp_parse_args( $params, $defaults );
+ $wc_configuration_manager->update_location( $args );
+
+ // @todo when is the best time to do this?
+ $wc_configuration_manager->set_smart_defaults();
+
+ return \rest_ensure_response( $this->fetch_all_data() );
+ }
+
+ /**
+ * Handler for setting Stripe settings.
+ *
+ * @param object $settings Stripe settings.
+ * @return WP_REST_Response with the latest settings.
+ */
+ public function update_stripe_settings( $settings ) {
+ if ( ! empty( $settings['activate'] ) ) {
+ // If activating the Stripe Gateway plugin, let's enable it.
+ $settings = [ 'enabled' => true ];
+ }
+ $result = Stripe_Connection::update_stripe_data( $settings );
+ if ( \is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ return $this->fetch_all_data();
+ }
+
+ /**
+ * Handler for setting additional settings.
+ *
+ * @param object $settings Settings.
+ * @return WP_REST_Response with the latest settings.
+ */
+ public function update_additional_settings( $settings ) {
+ if ( isset( $settings['allow_covering_fees'] ) ) {
+ update_option( 'newspack_donations_allow_covering_fees', $settings['allow_covering_fees'] );
+ }
+ if ( isset( $settings['allow_covering_fees_default'] ) ) {
+ update_option( 'newspack_donations_allow_covering_fees_default', $settings['allow_covering_fees_default'] );
+ }
+
+ if ( isset( $settings['allow_covering_fees_label'] ) ) {
+ update_option( 'newspack_donations_allow_covering_fees_label', $settings['allow_covering_fees_label'] );
+ }
+ if ( isset( $settings['fee_multiplier'] ) ) {
+ update_option( 'newspack_blocks_donate_fee_multiplier', $settings['fee_multiplier'] );
+ }
+ if ( isset( $settings['fee_static'] ) ) {
+ update_option( 'newspack_blocks_donate_fee_static', $settings['fee_static'] );
+ }
+ return $this->fetch_all_data();
+ }
+
+ /**
+ * Save Stripe settings.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Response.
+ */
+ public function api_update_stripe_settings( $request ) {
+ $params = $request->get_params();
+ $result = $this->update_stripe_settings( $params );
+ return \rest_ensure_response( $result );
+ }
+
+ /**
+ * Save WooPayments settings.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Response.
+ */
+ public function api_update_woopayments_settings( $request ) {
+ $wc_configuration_manager = Configuration_Managers::configuration_manager_class_for_plugin_slug( 'woocommerce' );
+
+ $params = $request->get_params();
+ $result = $wc_configuration_manager->update_wc_woopayments_settings( $params );
+ return \rest_ensure_response( $result );
+ }
+
+ /**
+ * Save additional payment method settings (e.g. transaction fees).
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Response.
+ */
+ public function api_update_additional_settings( $request ) {
+ $params = $request->get_params();
+ $result = $this->update_additional_settings( $params );
+ return \rest_ensure_response( $result );
+ }
+
+ /**
+ * Handler for setting the donation settings.
+ *
+ * @param object $settings Donation settings.
+ * @return WP_REST_Response with the latest settings.
+ */
+ public function update_donation_settings( $settings ) {
+ $donations_response = Donations::set_donation_settings( $settings );
+ if ( is_wp_error( $donations_response ) ) {
+ return rest_ensure_response( $donations_response );
+ }
+ return \rest_ensure_response( $this->fetch_all_data() );
+ }
+
+ /**
+ * API endpoint for setting the donation settings.
+ *
+ * @param WP_REST_Request $request Request containing settings.
+ * @return WP_REST_Response with the latest settings.
+ */
+ public function api_update_donation_settings( $request ) {
+ return $this->update_donation_settings( $request->get_params() );
+ }
+
+ /**
+ * API endpoint for setting Salesforce settings.
+ *
+ * @param WP_REST_Request $request Request containing settings.
+ * @return WP_REST_Response with the latest settings.
+ */
+ public function api_update_salesforce_settings( $request ) {
+ $salesforce_response = Salesforce::set_salesforce_settings( $request->get_params() );
+ if ( is_wp_error( $salesforce_response ) ) {
+ return rest_ensure_response( $salesforce_response );
+ }
+ return \rest_ensure_response( $this->fetch_all_data() );
+ }
+
+ /**
+ * Fetch all data needed to render the Wizard
+ *
+ * @return Array
+ */
+ public function fetch_all_data() {
+ $platform = Donations::get_platform_slug();
+ $wc_configuration_manager = Configuration_Managers::configuration_manager_class_for_plugin_slug( 'woocommerce' );
+ $wc_installed = 'active' === Plugin_Manager::get_managed_plugin_status( 'woocommerce' );
+ $stripe_data = Stripe_Connection::get_stripe_data();
+
+ $billing_fields = [];
+ if ( $wc_installed && Donations::is_platform_wc() ) {
+ $checkout = new \WC_Checkout();
+ $fields = $checkout->get_checkout_fields();
+ if ( ! empty( $fields['billing'] ) ) {
+ $billing_fields = $fields['billing'];
+ }
+ }
+
+ $args = [
+ 'country_state_fields' => newspack_get_countries(),
+ 'currency_fields' => newspack_get_currencies_options(),
+ 'location_data' => [],
+ 'payment_gateways' => [
+ 'stripe' => $stripe_data,
+ 'woopayments' => $wc_configuration_manager->woopayments_data(),
+ ],
+ 'additional_settings' => [
+ 'allow_covering_fees' => get_option( 'newspack_donations_allow_covering_fees', true ),
+ 'allow_covering_fees_default' => get_option( 'newspack_donations_allow_covering_fees_default', false ),
+ 'allow_covering_fees_label' => get_option( 'newspack_donations_allow_covering_fees_label', '' ),
+ 'fee_multiplier' => get_option( 'newspack_blocks_donate_fee_multiplier', '2.9' ),
+ 'fee_static' => get_option( 'newspack_blocks_donate_fee_static', '0.3' ),
+ ],
+ 'donation_data' => Donations::get_donation_settings(),
+ 'donation_page' => Donations::get_donation_page_info(),
+ 'available_billing_fields' => $billing_fields,
+ 'salesforce_settings' => [],
+ 'platform_data' => [
+ 'platform' => $platform,
+ ],
+ 'is_ssl' => is_ssl(),
+ 'errors' => [],
+ ];
+ if ( 'wc' === $platform ) {
+ $plugin_status = true;
+ $managed_plugins = Plugin_Manager::get_managed_plugins();
+ $required_plugins = [
+ 'woocommerce',
+ 'woocommerce-subscriptions',
+ ];
+ foreach ( $required_plugins as $required_plugin ) {
+ if ( 'active' !== $managed_plugins[ $required_plugin ]['Status'] ) {
+ $plugin_status = false;
+ }
+ }
+ $args = wp_parse_args(
+ [
+ 'salesforce_settings' => Salesforce::get_salesforce_settings(),
+ 'plugin_status' => $plugin_status,
+ ],
+ $args
+ );
+ } elseif ( Donations::is_platform_nrh() ) {
+ $nrh_config = NRH::get_settings();
+ $args['platform_data'] = wp_parse_args( $nrh_config, $args['platform_data'] );
+ }
+ return $args;
+ }
+
+ /**
+ * API endpoint for getting donation settings.
+ *
+ * @return WP_REST_Response containing info.
+ */
+ public function api_get_donation_settings() {
+ if ( Donations::is_platform_wc() ) {
+ $required_plugins_installed = $this->check_required_plugins_installed();
+ if ( is_wp_error( $required_plugins_installed ) ) {
+ return rest_ensure_response( $required_plugins_installed );
+ }
+ }
+
+ return rest_ensure_response( Donations::get_donation_settings() );
+ }
+
+ /**
+ * Check whether WooCommerce is installed and active.
+ *
+ * @return bool | WP_Error True on success, WP_Error on failure.
+ */
+ protected function check_required_plugins_installed() {
+ if ( ! function_exists( 'WC' ) ) {
+ return new WP_Error(
+ 'newspack_missing_required_plugin',
+ esc_html__( 'The WooCommerce plugin is not installed and activated. Install and/or activate it to access this feature.', 'newspack' ),
+ [
+ 'status' => 400,
+ 'level' => 'fatal',
+ ]
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate platform ID.
+ *
+ * @param mixed $value A param value.
+ * @return bool
+ */
+ public function api_validate_platform( $value ) {
+ return in_array( $value, [ 'nrh', 'wc', 'other' ] );
+ }
+}
diff --git a/src/wizards/audience/components/billing-fields/index.tsx b/src/wizards/audience/components/billing-fields/index.tsx
new file mode 100644
index 0000000000..e4bc900e88
--- /dev/null
+++ b/src/wizards/audience/components/billing-fields/index.tsx
@@ -0,0 +1,90 @@
+/**
+ * WordPress dependencies.
+ */
+import { useDispatch } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+import { CheckboxControl } from '@wordpress/components';
+
+/**
+ * Internal dependencies.
+ */
+import {
+ Button,
+ Card,
+ Grid,
+ SectionHeader,
+ Wizard,
+} from '../../../../components/src';
+
+const BillingFields = () => {
+ const wizardData = Wizard.useWizardData( 'reader-revenue' ) as ReaderRevenueWizardData;
+ const { updateWizardSettings, saveWizardSettings } = useDispatch( Wizard.STORE_NAMESPACE );
+
+ if ( ! wizardData.donation_data || 'errors' in wizardData.donation_data ) {
+ return null;
+ }
+
+ const changeHandler = ( path: string[] ) => ( value: any ) =>
+ updateWizardSettings( {
+ slug: 'newspack-reader-revenue-wizard',
+ path: [ 'donation_data', ...path ],
+ value,
+ } );
+
+ const onSave = () =>
+ saveWizardSettings( {
+ slug: 'newspack-reader-revenue-wizard',
+ section: 'donations',
+ payloadPath: [ 'donation_data' ],
+ } );
+
+ const availableFields = wizardData.available_billing_fields;
+ if ( ! availableFields || ! Object.keys( availableFields ).length ) {
+ return null;
+ }
+
+ const billingFields = wizardData.donation_data.billingFields.length
+ ? wizardData.donation_data.billingFields
+ : Object.keys( availableFields );
+
+ return (
+ <>
+
{ __( 'Set a page on your site as a donor landing page. Once a reader donates and lands on this page, they will be considered a donor.', @@ -90,7 +90,7 @@ const NRHSettings = () => { ) }
In progress.
- {/* TODO: Add Platform from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/`*/} - {/* TODO: Add Modal Checkout Billing Fields Settings from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/donations`*/} - {/* TODO: Add Stripe Setup from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/stripe-setup`*/} - {/* TODO: Add Saleforce Settings from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/salesforce`*/} -