array(
'name' => 'better-payment/payment-form',
'path' => 'payment-form',
'has_styles' => true,
'has_scripts' => true,
'render_callback' => 'render_payment_form_frontend',
'editor_data' => 'get_payment_form_editor_data',
),
'user-dashboard' => array(
'name' => 'better-payment/user-dashboard',
'path' => 'user-dashboard',
'has_styles' => true,
'has_scripts' => true,
'render_callback' => 'render_user_dashboard_frontend',
'editor_data' => 'get_user_dashboard_editor_data',
),
);
/**
* Get the singleton instance.
*
* @return BlockManager
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor to prevent direct instantiation.
*/
private function __construct() {
$this->init_hooks();
}
/**
* Initialize hooks.
*
* @return void
*/
private function init_hooks() {
add_action( 'init', array( $this, 'register_blocks' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
add_filter( 'block_categories_all', array( $this, 'register_block_category' ), 10, 2 );
}
/**
* Render callback for the payment form block frontend.
*
* This method contains all the rendering logic including layout template loading,
* settings mapping, widget proxy object creation, and security measures.
*
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
* @return string The rendered HTML output.
*/
public function render_payment_form_frontend( $attributes, $content = '', $block = null ) {
// Get block attributes with defaults.
$form_layout = isset( $attributes['formLayout'] ) ? sanitize_text_field( $attributes['formLayout'] ) : 'layout-1';
// Validate layout - only allow layout-1, layout-2, layout-3.
$allowed_layouts = array( 'layout-1', 'layout-2', 'layout-3' );
if ( ! in_array( $form_layout, $allowed_layouts, true ) ) {
$form_layout = 'layout-1';
}
// Check if layout file exists, fallback to layout-1 if not.
$template_file = BETTER_PAYMENT_ADMIN_VIEWS_PATH . '/elementor/layouts-block/' . $form_layout . '.php';
if ( ! file_exists( $template_file ) ) {
$template_file = BETTER_PAYMENT_ADMIN_VIEWS_PATH . '/elementor/layouts-block/layout-1.php';
if ( ! file_exists( $template_file ) ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'Better Payment: Layout template not found at ' . $template_file );
}
return '
' .
esc_html__( 'Payment form template not found.', 'better-payment' ) .
'
';
}
}
// Get global settings from database.
$global_settings = DB::get_settings();
// Generate a unique ID for this block instance.
// Use a deterministic ID based on block attributes to ensure consistency across page loads.
// This is critical for payment callback matching.
$block_id = isset( $attributes['blockId'] ) && ! empty( $attributes['blockId'] )
? sanitize_text_field( $attributes['blockId'] )
: 'bp-block-' . md5( get_the_ID() . wp_json_encode( $attributes ) );
// Build the $settings array that the layout files expect.
// This maps block attributes to the Elementor widget settings format.
$settings = $this->build_block_settings( $attributes, $global_settings, $form_layout );
// Check if at least one payment gateway is enabled.
$has_payment_gateway = 'yes' === $settings['better_payment_form_paypal_enable'] ||
'yes' === $settings['better_payment_form_stripe_enable'] ||
'yes' === $settings['better_payment_form_paystack_enable'];
// Build wrapper attributes using WordPress's block wrapper API.
// get_block_wrapper_attributes() reads supports.align from the block context
// (set automatically by WP_Block before invoking this render callback) and adds
// alignwide / alignfull to the class list — no manual $attributes['align'] needed.
$wrapper_class = 'better-payment-block bp-form-' . esc_attr( $form_layout );
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $wrapper_class ) );
if ( ! $has_payment_gateway ) {
return '' .
'
' .
'
' . esc_html__( 'Please configure at least one payment gateway (PayPal, Stripe, or Paystack) in Better Payment settings.', 'better-payment' ) . '
' .
'
';
}
// Enqueue scripts/styles before manage_response() so they are present even when the
// block returns the pending or success state early (the AJAX poller needs better-payment.js).
wp_enqueue_style( 'better-payment-el' );
wp_enqueue_style( 'bp-icon-front' );
wp_enqueue_style( 'better-payment-style' );
wp_enqueue_style( 'better-payment-common-style' );
wp_enqueue_style( 'better-payment-admin-style' );
$this->enqueue_font_awesome();
wp_enqueue_style( 'dashicons' );
wp_enqueue_script( 'better-payment-common-script' );
wp_enqueue_script( 'better-payment' );
// Match Elementor flow:
// - Handle response before rendering form.
// - On success: render success notice only.
// - On error: render error notice and continue rendering the form.
ob_start();
$payment_response = Handler::manage_response( $settings, $block_id );
$manage_response_output = ob_get_clean();
if ( $payment_response ) {
return '' . $manage_response_output . '
';
}
// Fire action hook for extensibility (matching Elementor widget behavior).
do_action( 'better_payment/elementor/editor/manage_response_webhook', null, $settings );
// Store settings in a transient so the AJAX handler can retrieve them.
// Only write when missing or expired — avoids a DB write on every page load.
$transient_key = 'bp_block_settings_' . get_the_ID() . '_' . $block_id;
if ( false === get_transient( $transient_key ) ) {
set_transient( $transient_key, $settings, HOUR_IN_SECONDS );
}
// Create widget proxy object for layout templates.
$widgetObj = $this->create_widget_proxy( $block_id, $settings );
// Prepare extraDatas for the layout.
$extraDatas = array(
'action' => esc_url( admin_url( 'admin-post.php' ) ),
'block_id' => $block_id,
'setting_meta' => wp_json_encode(
array(
'page_id' => get_the_ID(),
'widget_id' => $block_id,
'source' => 'gutenberg',
)
),
);
// Use output buffering to capture the layout output.
ob_start();
?>
>
' .
esc_html__( 'User Dashboard template not found.', 'better-payment' ) .
'';
}
}
// Generate unique block ID for this instance
$block_id = isset( $attributes['blockId'] ) && ! empty( $attributes['blockId'] )
? sanitize_text_field( $attributes['blockId'] )
: 'bp-user-dashboard-' . md5( get_the_ID() . wp_json_encode( $attributes ) );
// Map block attributes to widget settings format
$settings = $this->build_user_dashboard_settings( $attributes );
// Build wrapper attributes using WordPress's block wrapper API
$wrapper_class = 'better-payment-user-dashboard bp-' . esc_attr( $block_id ) . ' bp-dashboard-' . esc_attr( $dashboard_layout );
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $wrapper_class ) );
// Prepare extra data
$extraDatas = array(
'action' => esc_url( admin_url( 'admin-post.php' ) ),
'block_id' => $block_id,
'setting_meta' => wp_json_encode(
array(
'page_id' => get_the_ID(),
'widget_id' => $block_id,
'source' => 'gutenberg',
)
),
);
// Enqueue necessary scripts and styles
wp_enqueue_style( 'better-payment-el' );
wp_enqueue_style( 'bp-icon-front' );
wp_enqueue_style( 'better-payment-style' );
wp_enqueue_style( 'better-payment-common-style' );
wp_enqueue_style( 'better-payment-admin-style' );
$this->enqueue_font_awesome();
wp_enqueue_style( 'dashicons' );
wp_enqueue_style( 'bp-dashboard-pagination-style' );
wp_enqueue_script( 'better-payment-common-script' );
wp_enqueue_script( 'better-payment' );
if ( is_user_logged_in() ) {
wp_localize_script( 'better-payment', 'betterPaymentUserDash', [
'nonce' => wp_create_nonce( 'wp_rest' ),
'restUrl' => get_rest_url( null, 'better-payment/v1/user-transactions' ),
] );
}
wp_enqueue_script( 'bp-dashboard-pagination' );
// Use output buffering to capture the layout output
ob_start();
?>
>
create_user_dashboard_widget_proxy( $block_id, $settings );
// Create a closure to include the template with isolated scope
$render_layout = function ( $template_file, $settings, $widgetObj, $extraDatas ) {
// Make $bp_settings available to templates (templates expect this variable)
$bp_settings = $settings;
include $template_file;
};
// Bind the closure to the widget proxy so $this works in the template
$bound_render = \Closure::bind( $render_layout, $widgetObj, get_class( $widgetObj ) );
$bound_render( $template_file, $settings, $widgetObj, $extraDatas );
?>
isset( $attributes['sidebarShow'] ) ? (bool) $attributes['sidebarShow'] : true,
'avatar_show' => isset( $attributes['avatarShow'] ) ? (bool) $attributes['avatarShow'] : true,
'username_show' => isset( $attributes['usernameShow'] ) ? (bool) $attributes['usernameShow'] : true,
'dashboard_show' => isset( $attributes['dashboardShow'] ) ? (bool) $attributes['dashboardShow'] : true,
'transactions_show' => isset( $attributes['transactionsShow'] ) ? (bool) $attributes['transactionsShow'] : true,
'subscriptions_show' => isset( $attributes['subscriptionsShow'] ) ? (bool) $attributes['subscriptionsShow'] : true,
'header_show' => isset( $attributes['headerShow'] ) ? (bool) $attributes['headerShow'] : true,
// Labels
'dashboard_label' => isset( $attributes['dashboardLabel'] ) && ! empty( $attributes['dashboardLabel'] ) ? sanitize_text_field( $attributes['dashboardLabel'] ) : 'Dashboard',
'transaction_label' => isset( $attributes['transactionLabel'] ) && ! empty( $attributes['transactionLabel'] ) ? sanitize_text_field( $attributes['transactionLabel'] ) : 'Transactions',
'subscription_label' => isset( $attributes['subscriptionLabel'] ) && ! empty( $attributes['subscriptionLabel'] ) ? sanitize_text_field( $attributes['subscriptionLabel'] ) : 'Subscriptions',
'refresh_stats_label' => isset( $attributes['refreshStatsLabel'] ) && ! empty( $attributes['refreshStatsLabel'] ) ? sanitize_text_field( $attributes['refreshStatsLabel'] ) : 'Refresh Stats',
'no_items_label' => isset( $attributes['noItemsLabel'] ) && ! empty( $attributes['noItemsLabel'] ) ? sanitize_text_field( $attributes['noItemsLabel'] ) : 'No records found!',
// Dashboard section visibility
'dashboard_transaction_summary_show' => isset( $attributes['dashboardTransactionSummaryShow'] ) ? (bool) $attributes['dashboardTransactionSummaryShow'] : true,
'dashboard_analytics_report_show' => isset( $attributes['dashboardAnalyticsReportShow'] ) ? (bool) $attributes['dashboardAnalyticsReportShow'] : true,
'dashboard_recent_transactions_show' => isset( $attributes['dashboardRecentTransactionsShow'] ) ? (bool) $attributes['dashboardRecentTransactionsShow'] : true,
'dashboard_recurring_subscription_show' => isset( $attributes['dashboardRecurringSubscriptionShow'] ) ? (bool) $attributes['dashboardRecurringSubscriptionShow'] : true,
'dashboard_split_subscription_show' => isset( $attributes['dashboardSplitSubscriptionShow'] ) ? (bool) $attributes['dashboardSplitSubscriptionShow'] : true,
// Dashboard labels
'dashboard_total_amount_label' => isset( $attributes['dashboardTotalAmountLabel'] ) && ! empty( $attributes['dashboardTotalAmountLabel'] ) ? sanitize_text_field( $attributes['dashboardTotalAmountLabel'] ) : 'Total Amount',
'dashboard_completed_amount_label' => isset( $attributes['dashboardCompletedAmountLabel'] ) && ! empty( $attributes['dashboardCompletedAmountLabel'] ) ? sanitize_text_field( $attributes['dashboardCompletedAmountLabel'] ) : 'Completed Amount',
'dashboard_incomplete_amount_label' => isset( $attributes['dashboardIncompleteAmountLabel'] ) && ! empty( $attributes['dashboardIncompleteAmountLabel'] ) ? sanitize_text_field( $attributes['dashboardIncompleteAmountLabel'] ) : 'Incomplete Amount',
'dashboard_refunded_amount_label' => isset( $attributes['dashboardRefundedAmountLabel'] ) && ! empty( $attributes['dashboardRefundedAmountLabel'] ) ? sanitize_text_field( $attributes['dashboardRefundedAmountLabel'] ) : 'Refunded Amount',
'dashboard_view_all_label' => isset( $attributes['dashboardViewAllLabel'] ) && ! empty( $attributes['dashboardViewAllLabel'] ) ? sanitize_text_field( $attributes['dashboardViewAllLabel'] ) : 'View All',
'dashboard_analytics_reports_label' => isset( $attributes['dashboardAnalyticsReportsLabel'] ) && ! empty( $attributes['dashboardAnalyticsReportsLabel'] ) ? sanitize_text_field( $attributes['dashboardAnalyticsReportsLabel'] ) : 'Analytics Reports',
'dashboard_recent_transactions_label' => isset( $attributes['dashboardRecentTransactionsLabel'] ) && ! empty( $attributes['dashboardRecentTransactionsLabel'] ) ? sanitize_text_field( $attributes['dashboardRecentTransactionsLabel'] ) : 'Recent Transactions',
'dashboard_recurring_subscriptions_label' => isset( $attributes['dashboardRecurringSubscriptionsLabel'] ) && ! empty( $attributes['dashboardRecurringSubscriptionsLabel'] ) ? sanitize_text_field( $attributes['dashboardRecurringSubscriptionsLabel'] ) : 'Recurring Subscriptions',
'dashboard_split_subscriptions_label' => isset( $attributes['dashboardSplitSubscriptionsLabel'] ) && ! empty( $attributes['dashboardSplitSubscriptionsLabel'] ) ? sanitize_text_field( $attributes['dashboardSplitSubscriptionsLabel'] ) : 'Split Subscriptions',
// Transaction table visibility
'transaction_table_name_show' => isset( $attributes['transactionTableNameShow'] ) ? (bool) $attributes['transactionTableNameShow'] : true,
'transaction_table_email_address_show' => isset( $attributes['transactionTableEmailAddressShow'] ) ? (bool) $attributes['transactionTableEmailAddressShow'] : true,
'transaction_table_amount_show' => isset( $attributes['transactionTableAmountShow'] ) ? (bool) $attributes['transactionTableAmountShow'] : true,
'transaction_table_payment_type_show' => isset( $attributes['transactionTablePaymentTypeShow'] ) ? (bool) $attributes['transactionTablePaymentTypeShow'] : true,
'transaction_table_transaction_id_show' => isset( $attributes['transactionTableTransactionIdShow'] ) ? (bool) $attributes['transactionTableTransactionIdShow'] : true,
'transaction_table_source_show' => isset( $attributes['transactionTableSourceShow'] ) ? (bool) $attributes['transactionTableSourceShow'] : true,
'transaction_table_status_show' => isset( $attributes['transactionTableStatusShow'] ) ? (bool) $attributes['transactionTableStatusShow'] : true,
'transaction_table_date_show' => isset( $attributes['transactionTableDateShow'] ) ? (bool) $attributes['transactionTableDateShow'] : true,
'transaction_table_action_show' => isset( $attributes['transactionTableActionShow'] ) ? (bool) $attributes['transactionTableActionShow'] : true,
// Transaction table labels
'transaction_table_name_label' => isset( $attributes['transactionTableNameLabel'] ) && ! empty( $attributes['transactionTableNameLabel'] ) ? sanitize_text_field( $attributes['transactionTableNameLabel'] ) : 'Name',
'transaction_table_email_address_label' => isset( $attributes['transactionTableEmailAddressLabel'] ) && ! empty( $attributes['transactionTableEmailAddressLabel'] ) ? sanitize_text_field( $attributes['transactionTableEmailAddressLabel'] ) : 'Email Address',
'transaction_table_amount_label' => isset( $attributes['transactionTableAmountLabel'] ) && ! empty( $attributes['transactionTableAmountLabel'] ) ? sanitize_text_field( $attributes['transactionTableAmountLabel'] ) : 'Amount',
'transaction_table_payment_type_label' => isset( $attributes['transactionTablePaymentTypeLabel'] ) && ! empty( $attributes['transactionTablePaymentTypeLabel'] ) ? sanitize_text_field( $attributes['transactionTablePaymentTypeLabel'] ) : 'Payment Type',
'transaction_table_transaction_id_label' => isset( $attributes['transactionTableTransactionIdLabel'] ) && ! empty( $attributes['transactionTableTransactionIdLabel'] ) ? sanitize_text_field( $attributes['transactionTableTransactionIdLabel'] ) : 'Transaction ID',
'transaction_table_source_label' => isset( $attributes['transactionTableSourceLabel'] ) && ! empty( $attributes['transactionTableSourceLabel'] ) ? sanitize_text_field( $attributes['transactionTableSourceLabel'] ) : 'Source',
'transaction_table_status_label' => isset( $attributes['transactionTableStatusLabel'] ) && ! empty( $attributes['transactionTableStatusLabel'] ) ? sanitize_text_field( $attributes['transactionTableStatusLabel'] ) : 'Status',
'transaction_table_date_label' => isset( $attributes['transactionTableDateLabel'] ) && ! empty( $attributes['transactionTableDateLabel'] ) ? sanitize_text_field( $attributes['transactionTableDateLabel'] ) : 'Date',
'transaction_table_action_label' => isset( $attributes['transactionTableActionLabel'] ) && ! empty( $attributes['transactionTableActionLabel'] ) ? sanitize_text_field( $attributes['transactionTableActionLabel'] ) : 'Action',
// Subscription table column visibility (pro feature — consumed by template-subscriptions-tab.php)
'subscription_table_subscription_id_show' => isset( $attributes['subscriptionTableSubscriptionIdShow'] ) ? (bool) $attributes['subscriptionTableSubscriptionIdShow'] : true,
'subscription_table_plan_id_show' => isset( $attributes['subscriptionTablePlanIdShow'] ) ? (bool) $attributes['subscriptionTablePlanIdShow'] : true,
'subscription_table_status_show' => isset( $attributes['subscriptionTableStatusShow'] ) ? (bool) $attributes['subscriptionTableStatusShow'] : true,
'subscription_table_amount_show' => isset( $attributes['subscriptionTableAmountShow'] ) ? (bool) $attributes['subscriptionTableAmountShow'] : true,
'subscription_table_created_date_show' => isset( $attributes['subscriptionTableCreatedDateShow'] ) ? (bool) $attributes['subscriptionTableCreatedDateShow'] : true,
'subscription_table_current_period_show' => isset( $attributes['subscriptionTableCurrentPeriodShow'] ) ? (bool) $attributes['subscriptionTableCurrentPeriodShow'] : true,
'subscription_table_action_show' => isset( $attributes['subscriptionTableActionShow'] ) ? (bool) $attributes['subscriptionTableActionShow'] : true,
// Subscription table column labels (pro feature)
'subscription_table_subscription_id_label' => ! empty( $attributes['subscriptionTableSubscriptionIdLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableSubscriptionIdLabel'] ) : 'Subscription ID',
'subscription_table_plan_id_label' => ! empty( $attributes['subscriptionTablePlanIdLabel'] ) ? sanitize_text_field( $attributes['subscriptionTablePlanIdLabel'] ) : 'Product Name',
'subscription_table_status_label' => ! empty( $attributes['subscriptionTableStatusLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableStatusLabel'] ) : 'Status',
'subscription_table_amount_label' => ! empty( $attributes['subscriptionTableAmountLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableAmountLabel'] ) : 'Amount',
'subscription_table_created_date_label' => ! empty( $attributes['subscriptionTableCreatedDateLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableCreatedDateLabel'] ) : 'Payment Date',
'subscription_table_current_period_label' => ! empty( $attributes['subscriptionTableCurrentPeriodLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableCurrentPeriodLabel'] ) : 'Renewal Date',
'subscription_table_action_label' => ! empty( $attributes['subscriptionTableActionLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableActionLabel'] ) : 'Action',
'subscription_table_status_active_label' => ! empty( $attributes['subscriptionTableStatusActiveLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableStatusActiveLabel'] ) : 'Active',
'subscription_table_status_inactive_label' => ! empty( $attributes['subscriptionTableStatusInactiveLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableStatusInactiveLabel'] ) : 'Inactive',
'subscription_table_action_cancel_label' => ! empty( $attributes['subscriptionTableActionCancelLabel'] ) ? sanitize_text_field( $attributes['subscriptionTableActionCancelLabel'] ) : 'Cancel',
);
/**
* Block-specific filter for merging pro $bp_settings.
*
* Passes raw camelCase block $attributes as second arg (unlike the Elementor filter
* which passes raw Elementor settings with better_payment_user_dashboard_* keys).
*
* @since 3.x.x
*
* @param array $settings The built settings array.
* @param array $attributes The raw block attributes (camelCase).
*/
return apply_filters( 'better_payment/block/user_dashboard/bp_settings', $settings, $attributes );
}
/**
* Create a widget proxy object for user dashboard layout templates.
*
* This provides the methods that layout files expect from $widgetObj and $this.
*
* @param string $block_id The unique block ID.
* @param array $settings The settings array.
* @return object Widget proxy object with required methods.
*/
private function create_user_dashboard_widget_proxy( $block_id, $settings ) {
return new class( $block_id, $settings ) {
/**
* Block ID.
*
* @var string
*/
private $id;
/**
* Settings array.
*
* @var array
*/
private $settings;
/**
* Pro enabled flag.
*
* @var bool
*/
public $pro_enabled;
/**
* Constructor.
*
* @param string $id Block ID.
* @param array $settings Settings array.
*/
public function __construct( $id, $settings ) {
$this->id = $id;
$this->settings = $settings;
$this->pro_enabled = apply_filters( 'better_payment/pro_enabled', false );
}
/**
* Get the block ID.
*
* @return string Block ID.
*/
public function get_id() {
return $this->id;
}
/**
* Get user transactions by email.
*
* @param string $email User email (uses current user if empty).
* @return array Transaction records.
*/
public function get_user_transactions( $email = '', $page = 1, $per_page = 10 ) {
$current_user = wp_get_current_user();
if ( empty( $email ) ) {
$email = $current_user->user_email;
}
return \Better_Payment\Lite\Admin\DB::get_user_transactions_paginated( $email, [
'page' => $page,
'per_page' => $per_page,
'type' => 'transactions',
] );
}
/**
* Get analytics data for transactions.
*
* @param array $transactions Transactions array.
* @return array Analytics data (counts and amounts by status).
*/
public function get_transactions_analytics( $transactions = array() ) {
return \Better_Payment\Lite\Admin\DB::get_transactions_analytics_dashboard( $transactions );
}
};
}
/**
* Build the settings array for layout templates.
*
* Maps block attributes to the Elementor widget settings format expected by layout templates.
*
* @param array $attributes Block attributes.
* @param array $global_settings Global plugin settings from database.
* @param string $form_layout The selected form layout.
* @return array Settings array for layout templates.
*/
public function build_block_settings( $attributes, $global_settings, $form_layout ) {
// Convert block formFields array to Elementor format.
$form_fields = $this->convert_form_fields( $attributes );
// Convert block amountList array to Elementor format.
$amount_list = $this->convert_amount_list( $attributes );
// Get block attribute values with fallbacks to global settings.
$paypal_enabled = isset( $attributes['paypalEnabled'] )
? ( $attributes['paypalEnabled'] ? 'yes' : '' )
: ( ! empty( $global_settings['better_payment_settings_general_general_paypal'] ) && 'yes' === $global_settings['better_payment_settings_general_general_paypal'] ? 'yes' : '' );
// Get PayPal business email from block attribute, fall back to global settings.
$paypal_business_email = isset( $attributes['paypalBusinessEmail'] ) && ! empty( $attributes['paypalBusinessEmail'] )
? sanitize_email( $attributes['paypalBusinessEmail'] )
: ( ! empty( $global_settings['better_payment_settings_payment_paypal_email'] ) ? $global_settings['better_payment_settings_payment_paypal_email'] : '' );
$stripe_enabled = isset( $attributes['stripeEnabled'] )
? ( $attributes['stripeEnabled'] ? 'yes' : '' )
: ( ! empty( $global_settings['better_payment_settings_general_general_stripe'] ) && 'yes' === $global_settings['better_payment_settings_general_general_stripe'] ? 'yes' : '' );
$paystack_enabled = isset( $attributes['paystackEnabled'] )
? ( $attributes['paystackEnabled'] ? 'yes' : '' )
: ( ! empty( $global_settings['better_payment_settings_general_general_paystack'] ) && 'yes' === $global_settings['better_payment_settings_general_general_paystack'] ? 'yes' : '' );
// Get currency from block attribute or global settings.
$currency = isset( $attributes['currency'] ) && ! empty( $attributes['currency'] )
? sanitize_text_field( $attributes['currency'] )
: ( ! empty( $global_settings['better_payment_settings_general_general_currency'] ) ? $global_settings['better_payment_settings_general_general_currency'] : 'USD' );
// Get sidebar show setting from block attribute.
$sidebar_show = isset( $attributes['showSidebar'] )
? ( $attributes['showSidebar'] ? 'yes' : '' )
: 'yes';
// Get show amount list from block attribute.
$show_amount_list = isset( $attributes['showAmountList'] )
? ( $attributes['showAmountList'] ? 'yes' : '' )
: '';
// Get currency alignment from block attribute.
$currency_alignment = isset( $attributes['currencyAlign'] ) && ! empty( $attributes['currencyAlign'] )
? sanitize_text_field( $attributes['currencyAlign'] )
: 'left';
// Get payment source from block attribute.
$payment_source = isset( $attributes['paymentSource'] ) && ! empty( $attributes['paymentSource'] )
? sanitize_text_field( $attributes['paymentSource'] )
: '';
// Get transaction details from block attributes.
$transaction_title = isset( $attributes['transactionTitle'] ) && ! empty( $attributes['transactionTitle'] )
? sanitize_text_field( $attributes['transactionTitle'] )
: __( 'Transaction Details', 'better-payment' );
$transaction_sub_title = isset( $attributes['transactionSubTitle'] ) && ! empty( $attributes['transactionSubTitle'] )
? sanitize_text_field( $attributes['transactionSubTitle'] )
: __( 'Total payment of your product in the following:', 'better-payment' );
$amount_text = isset( $attributes['amountText'] ) && ! empty( $attributes['amountText'] )
? sanitize_text_field( $attributes['amountText'] )
: __( 'Amount:', 'better-payment' );
$product_title = isset( $attributes['transactionDetailsProductTitle'] ) && ! empty( $attributes['transactionDetailsProductTitle'] )
? sanitize_text_field( $attributes['transactionDetailsProductTitle'] )
: __( 'Title:', 'better-payment' );
// Get button text from block attributes.
$paypal_button_text = isset( $attributes['paypalButtonText'] ) && ! empty( $attributes['paypalButtonText'] )
? sanitize_text_field( $attributes['paypalButtonText'] )
: '';
$stripe_button_text = isset( $attributes['stripeButtonText'] ) && ! empty( $attributes['stripeButtonText'] )
? sanitize_text_field( $attributes['stripeButtonText'] )
: '';
$paystack_button_text = isset( $attributes['paystackButtonText'] ) && ! empty( $attributes['paystackButtonText'] )
? sanitize_text_field( $attributes['paystackButtonText'] )
: '';
// Get product IDs from block attributes.
$woocommerce_product_id = isset( $attributes['woocommerceProductId'] ) && ! empty( $attributes['woocommerceProductId'] )
? intval( $attributes['woocommerceProductId'] )
: 0;
$fluentcart_product_id = isset( $attributes['fluentcartProductId'] ) && ! empty( $attributes['fluentcartProductId'] )
? intval( $attributes['fluentcartProductId'] )
: 0;
// Get Stripe Price ID from block attributes.
// Block attribute: stripeDefaultPriceId (string)
// Used when payment source is 'stripe' (Stripe Product).
$stripe_price_id = isset( $attributes['stripeDefaultPriceId'] ) && ! empty( $attributes['stripeDefaultPriceId'] )
? sanitize_text_field( $attributes['stripeDefaultPriceId'] )
: '';
// Validate Stripe Price ID format (should start with 'price_').
if ( ! empty( $stripe_price_id ) && strpos( $stripe_price_id, 'price_' ) !== 0 ) {
// Invalid format - log warning in debug mode and reset to empty.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'Better Payment: Invalid Stripe Price ID format (should start with "price_"): ' . esc_html( $stripe_price_id ) );
}
$stripe_price_id = '';
}
// Get email notification setting from block attributes.
// The editor default for emailNotificationEnabled is true, but WordPress does not
// serialize default attribute values into the block markup and the registered
// block.json declares no schema default, so the attribute is absent at render time
// unless explicitly toggled. Treat "unset" as enabled to match the editor default,
// while still honoring an explicit OFF (serialized as false).
$email_notification_enabled = ! isset( $attributes['emailNotificationEnabled'] ) || $attributes['emailNotificationEnabled']
? 'yes'
: '';
// Get site domain for default email addresses.
$site_url_parsed = wp_parse_url( get_site_url() );
$site_domain = ! empty( $site_url_parsed['host'] ) ? esc_html( $site_url_parsed['host'] ) : 'example.com';
$default_from_email = 'wordpress@' . $site_domain;
$default_email_subject = sprintf( __( 'Better Payment transaction on %s', 'better-payment' ), esc_html( get_option( 'blogname' ) ) );
// Admin email settings from block attributes with fallback to global settings.
// Block attribute: adminEmail (string)
$email_to = isset( $attributes['adminEmail'] ) && ! empty( $attributes['adminEmail'] )
? $this->sanitize_email_list( $attributes['adminEmail'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_to'] ) ? sanitize_email( $global_settings['better_payment_settings_general_email_to'] ) : sanitize_email( get_option( 'admin_email' ) ) );
// Block attribute: adminSubject (string)
$email_subject = isset( $attributes['adminSubject'] ) && ! empty( $attributes['adminSubject'] )
? sanitize_text_field( $attributes['adminSubject'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_subject'] ) ? sanitize_text_field( $global_settings['better_payment_settings_general_email_subject'] ) : $default_email_subject );
// Block attribute: adminMessage (string)
$email_content = isset( $attributes['adminMessage'] ) && ! empty( $attributes['adminMessage'] )
? wp_kses_post( $attributes['adminMessage'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_message_admin'] ) ? wp_kses_post( $global_settings['better_payment_settings_general_email_message_admin'] ) : '' );
// Block attribute: adminFromEmail (string)
$email_from = isset( $attributes['adminFromEmail'] ) && ! empty( $attributes['adminFromEmail'] )
? sanitize_email( $attributes['adminFromEmail'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_from_email'] ) ? sanitize_email( $global_settings['better_payment_settings_general_email_from_email'] ) : $default_from_email );
// Block attribute: adminFromName (string)
$email_from_name = isset( $attributes['adminFromName'] ) && ! empty( $attributes['adminFromName'] )
? sanitize_text_field( $attributes['adminFromName'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_from_name'] ) ? sanitize_text_field( $global_settings['better_payment_settings_general_email_from_name'] ) : esc_html( get_bloginfo( 'name' ) ) );
// Block attribute: adminReplyTo (string)
$email_reply_to = isset( $attributes['adminReplyTo'] ) && ! empty( $attributes['adminReplyTo'] )
? sanitize_email( $attributes['adminReplyTo'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_reply_to'] ) ? sanitize_email( $global_settings['better_payment_settings_general_email_reply_to'] ) : $default_from_email );
// Block attribute: adminCc (string)
$email_cc = isset( $attributes['adminCc'] ) && ! empty( $attributes['adminCc'] )
? $this->sanitize_email_list( $attributes['adminCc'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_cc'] ) ? $this->sanitize_email_list( $global_settings['better_payment_settings_general_email_cc'] ) : '' );
// Block attribute: adminBcc (string)
$email_bcc = isset( $attributes['adminBcc'] ) && ! empty( $attributes['adminBcc'] )
? $this->sanitize_email_list( $attributes['adminBcc'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_bcc'] ) ? $this->sanitize_email_list( $global_settings['better_payment_settings_general_email_bcc'] ) : '' );
// Block attribute: adminSendAs (string: 'html' or 'plain')
$allowed_content_types = array( 'html', 'plain' );
$email_content_type = isset( $attributes['adminSendAs'] ) && in_array( $attributes['adminSendAs'], $allowed_content_types, true )
? $attributes['adminSendAs']
: ( ! empty( $global_settings['better_payment_settings_general_email_send_as'] ) && in_array( $global_settings['better_payment_settings_general_email_send_as'], $allowed_content_types, true ) ? $global_settings['better_payment_settings_general_email_send_as'] : 'html' );
// Admin email display toggles (boolean attributes converted to 'yes'/'' for Elementor compatibility).
// Block attribute: adminShowHeaderText (boolean, default: true)
$email_content_heading = isset( $attributes['adminShowHeaderText'] ) ? ( $attributes['adminShowHeaderText'] ? 'yes' : '' ) : 'yes';
// Block attribute: adminShowFromSection (boolean, default: true)
$email_content_from_section = isset( $attributes['adminShowFromSection'] ) ? ( $attributes['adminShowFromSection'] ? 'yes' : '' ) : 'yes';
// Block attribute: adminShowToSection (boolean, default: true)
$email_content_to_section = isset( $attributes['adminShowToSection'] ) ? ( $attributes['adminShowToSection'] ? 'yes' : '' ) : 'yes';
// Block attribute: adminShowTransactionSummary (boolean, default: true)
$email_content_transaction_summary = isset( $attributes['adminShowTransactionSummary'] ) ? ( $attributes['adminShowTransactionSummary'] ? 'yes' : '' ) : 'yes';
// Block attribute: adminShowFooterText (boolean, default: true)
$email_content_footer_text = isset( $attributes['adminShowFooterText'] ) ? ( $attributes['adminShowFooterText'] ? 'yes' : '' ) : 'yes';
// Customer email settings from block attributes with fallback to global settings.
// Block attribute: customerSubject (string)
$email_subject_customer = isset( $attributes['customerSubject'] ) && ! empty( $attributes['customerSubject'] )
? sanitize_text_field( $attributes['customerSubject'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_subject_customer'] ) ? sanitize_text_field( $global_settings['better_payment_settings_general_email_subject_customer'] ) : $default_email_subject );
// Block attribute: customerMessage (string)
$email_content_customer = isset( $attributes['customerMessage'] ) && ! empty( $attributes['customerMessage'] )
? wp_kses_post( $attributes['customerMessage'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_message_customer'] ) ? wp_kses_post( $global_settings['better_payment_settings_general_email_message_customer'] ) : '' );
// Block attribute: customerFromEmail (string)
$email_from_customer = isset( $attributes['customerFromEmail'] ) && ! empty( $attributes['customerFromEmail'] )
? sanitize_email( $attributes['customerFromEmail'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_from_email_customer'] ) ? sanitize_email( $global_settings['better_payment_settings_general_email_from_email_customer'] ) : $default_from_email );
// Block attribute: customerFromName (string)
$email_from_name_customer = isset( $attributes['customerFromName'] ) && ! empty( $attributes['customerFromName'] )
? sanitize_text_field( $attributes['customerFromName'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_from_name_customer'] ) ? sanitize_text_field( $global_settings['better_payment_settings_general_email_from_name_customer'] ) : esc_html( get_bloginfo( 'name' ) ) );
// Block attribute: customerReplyTo (string)
$email_reply_to_customer = isset( $attributes['customerReplyTo'] ) && ! empty( $attributes['customerReplyTo'] )
? sanitize_email( $attributes['customerReplyTo'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_reply_to_customer'] ) ? sanitize_email( $global_settings['better_payment_settings_general_email_reply_to_customer'] ) : $default_from_email );
// Block attribute: customerCc (string)
$email_cc_customer = isset( $attributes['customerCc'] ) && ! empty( $attributes['customerCc'] )
? $this->sanitize_email_list( $attributes['customerCc'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_cc_customer'] ) ? $this->sanitize_email_list( $global_settings['better_payment_settings_general_email_cc_customer'] ) : '' );
// Block attribute: customerBcc (string)
$email_bcc_customer = isset( $attributes['customerBcc'] ) && ! empty( $attributes['customerBcc'] )
? $this->sanitize_email_list( $attributes['customerBcc'] )
: ( ! empty( $global_settings['better_payment_settings_general_email_bcc_customer'] ) ? $this->sanitize_email_list( $global_settings['better_payment_settings_general_email_bcc_customer'] ) : '' );
// Block attribute: customerSendAs (string: 'html' or 'plain')
$email_content_type_customer = isset( $attributes['customerSendAs'] ) && in_array( $attributes['customerSendAs'], $allowed_content_types, true )
? $attributes['customerSendAs']
: ( ! empty( $global_settings['better_payment_settings_general_email_send_as_customer'] ) && in_array( $global_settings['better_payment_settings_general_email_send_as_customer'], $allowed_content_types, true ) ? $global_settings['better_payment_settings_general_email_send_as_customer'] : 'html' );
// Customer email attachment settings.
// Block attribute: customerPDFAttachment (boolean, default: false)
$email_attachment_pdf_show = isset( $attributes['customerPDFAttachment'] ) && $attributes['customerPDFAttachment'] ? 'yes' : '';
// Block attribute: customerFileAttachment (string - URL)
$email_attachment = isset( $attributes['customerFileAttachment'] ) && ! empty( $attributes['customerFileAttachment'] )
? array( 'url' => esc_url( $attributes['customerFileAttachment'] ) )
: array();
// Get success message settings from block attributes.
$success_icon = isset( $attributes['successIcon'] ) && ! empty( $attributes['successIcon'] )
? sanitize_text_field( $attributes['successIcon'] )
: '';
$success_heading = isset( $attributes['successHeading'] ) && ! empty( $attributes['successHeading'] )
? sanitize_text_field( $attributes['successHeading'] )
: '';
$success_sub_heading = isset( $attributes['successSubHeading'] ) && ! empty( $attributes['successSubHeading'] )
? sanitize_text_field( $attributes['successSubHeading'] )
: '';
$transaction_id_text = isset( $attributes['transactionID'] ) && ! empty( $attributes['transactionID'] )
? sanitize_text_field( $attributes['transactionID'] )
: __( 'Transaction ID:', 'better-payment' );
$thanks_message = isset( $attributes['thanksMessage'] ) && ! empty( $attributes['thanksMessage'] )
? sanitize_text_field( $attributes['thanksMessage'] )
: __( 'Thank you for your payment.', 'better-payment' );
$amount_message = isset( $attributes['amountMessage'] ) && ! empty( $attributes['amountMessage'] )
? sanitize_text_field( $attributes['amountMessage'] )
: __( 'Amount', 'better-payment' );
$currency_message = isset( $attributes['currencyMessage'] ) && ! empty( $attributes['currencyMessage'] )
? sanitize_text_field( $attributes['currencyMessage'] )
: __( 'Currency', 'better-payment' );
$payment_method_message = isset( $attributes['paymentMethodMessage'] ) && ! empty( $attributes['paymentMethodMessage'] )
? sanitize_text_field( $attributes['paymentMethodMessage'] )
: __( 'Payment Method', 'better-payment' );
$payment_type_message = isset( $attributes['paymentType'] ) && ! empty( $attributes['paymentType'] )
? sanitize_text_field( $attributes['paymentType'] )
: __( 'Payment Type', 'better-payment' );
$merchant_details_text = isset( $attributes['merchatDetails'] ) && ! empty( $attributes['merchatDetails'] )
? sanitize_text_field( $attributes['merchatDetails'] )
: __( 'Merchant Details', 'better-payment' );
$paid_amount_text = isset( $attributes['paidAmount'] ) && ! empty( $attributes['paidAmount'] )
? sanitize_text_field( $attributes['paidAmount'] )
: __( 'Paid Amount', 'better-payment' );
$purchase_details_text = isset( $attributes['purchaseDetails'] ) && ! empty( $attributes['purchaseDetails'] )
? sanitize_text_field( $attributes['purchaseDetails'] )
: __( 'Purchase Details', 'better-payment' );
$print_btn_text = isset( $attributes['printButtonText'] ) && ! empty( $attributes['printButtonText'] )
? sanitize_text_field( $attributes['printButtonText'] )
: __( 'Print', 'better-payment' );
$view_details_btn_text = isset( $attributes['viewDetailsButtonText'] ) && ! empty( $attributes['viewDetailsButtonText'] )
? sanitize_text_field( $attributes['viewDetailsButtonText'] )
: __( 'View Details', 'better-payment' );
$user_dashboard_url = isset( $attributes['userDashboardUrl'] ) && ! empty( $attributes['userDashboardUrl'] )
? esc_url( $attributes['userDashboardUrl'] )
: '';
$custom_redirect_url = isset( $attributes['customRedirectUrl'] ) && ! empty( $attributes['customRedirectUrl'] )
? esc_url( $attributes['customRedirectUrl'] )
: '';
// Get error message settings from block attributes.
$error_icon = isset( $attributes['errorIcon'] ) && ! empty( $attributes['errorIcon'] )
? sanitize_text_field( $attributes['errorIcon'] )
: '';
// Email icon (block control) maps to Elementor email logo (image URL) when uploaded.
$email_icon = isset( $attributes['emailIcon'] ) && ! empty( $attributes['emailIcon'] )
? sanitize_text_field( $attributes['emailIcon'] )
: 'bp-icon bp-envelope';
$error_heading = isset( $attributes['errorHeading'] ) && ! empty( $attributes['errorHeading'] )
? sanitize_text_field( $attributes['errorHeading'] )
: __( 'Payment Failed', 'better-payment' );
$error_sub_heading = isset( $attributes['errorSubHeading'] ) && ! empty( $attributes['errorSubHeading'] )
? sanitize_text_field( $attributes['errorSubHeading'] )
: __( 'Your payment has failed. Please check your payment details', 'better-payment' );
$error_transaction_id_text = isset( $attributes['transactionIDText'] ) && ! empty( $attributes['transactionIDText'] )
? sanitize_text_field( $attributes['transactionIDText'] )
: __( 'Transaction ID', 'better-payment' );
$error_show_details_button = isset( $attributes['showDetailsButton'] ) && $attributes['showDetailsButton'] ? 'yes' : '';
$error_details_button_text = isset( $attributes['detailsButtonText'] ) && ! empty( $attributes['detailsButtonText'] )
? sanitize_text_field( $attributes['detailsButtonText'] )
: __( 'View Details', 'better-payment' );
$error_details_button_url = isset( $attributes['detailsButtonUrl'] ) && ! empty( $attributes['detailsButtonUrl'] )
? esc_url( $attributes['detailsButtonUrl'] )
: '';
$error_redirect_url = isset( $attributes['errorRedirectUrl'] ) && ! empty( $attributes['errorRedirectUrl'] )
? esc_url( $attributes['errorRedirectUrl'] )
: '';
return array(
// Layout settings.
'better_payment_form_layout' => $form_layout,
// Form title - used as item/product name in PayPal and Stripe checkout.
// Block attribute is 'formName' (not 'formTitle') — default is 'Better Payment'.
'better_payment_form_title' => isset( $attributes['formName'] ) && ! empty( $attributes['formName'] )
? sanitize_text_field( $attributes['formName'] )
: '',
// Payment gateway settings - prioritize block attributes over global settings.
'better_payment_form_paypal_enable' => $paypal_enabled,
'better_payment_form_stripe_enable' => $stripe_enabled,
'better_payment_form_paystack_enable' => $paystack_enabled,
// Stripe API keys - resolve based on live mode (matches Elementor behavior).
// Handler::stripe_payment_success() uses better_payment_stripe_secret_key directly.
'better_payment_stripe_live_mode' => ! empty( $global_settings['better_payment_settings_payment_stripe_live_mode'] ) && 'yes' === $global_settings['better_payment_settings_payment_stripe_live_mode'] ? 'yes' : '',
'better_payment_stripe_public_key' => $this->resolve_stripe_key( $global_settings, 'public' ),
'better_payment_stripe_secret_key' => $this->resolve_stripe_key( $global_settings, 'secret' ),
'better_payment_stripe_public_key_live' => ! empty( $global_settings['better_payment_settings_payment_stripe_live_public'] ) ? $global_settings['better_payment_settings_payment_stripe_live_public'] : '',
'better_payment_stripe_secret_key_live' => ! empty( $global_settings['better_payment_settings_payment_stripe_live_secret'] ) ? $global_settings['better_payment_settings_payment_stripe_live_secret'] : '',
// PayPal API keys - prioritize block attributes over global settings.
'better_payment_paypal_live_mode' => ! empty( $global_settings['better_payment_settings_payment_paypal_live_mode'] ) && 'yes' === $global_settings['better_payment_settings_payment_paypal_live_mode'] ? 'yes' : '',
'better_payment_paypal_business_email' => $paypal_business_email,
'better_payment_paypal_button_type' => '_xclick', // Default PayPal button type for Buy Now functionality.
// Paystack API keys - resolve based on live mode (matches Elementor behavior).
'better_payment_paystack_live_mode' => ! empty( $global_settings['better_payment_settings_payment_paystack_live_mode'] ) && 'yes' === $global_settings['better_payment_settings_payment_paystack_live_mode'] ? 'yes' : '',
'better_payment_paystack_public_key' => $this->resolve_paystack_key( $global_settings, 'public' ),
'better_payment_paystack_secret_key' => $this->resolve_paystack_key( $global_settings, 'secret' ),
'better_payment_paystack_public_key_live' => ! empty( $global_settings['better_payment_settings_payment_paystack_live_public'] ) ? $global_settings['better_payment_settings_payment_paystack_live_public'] : '',
'better_payment_paystack_secret_key_live' => ! empty( $global_settings['better_payment_settings_payment_paystack_live_secret'] ) ? $global_settings['better_payment_settings_payment_paystack_live_secret'] : '',
// Currency settings from block attributes with fallback to global settings.
'better_payment_form_currency' => $currency,
'better_payment_form_currency_alignment' => $currency_alignment,
// Payment source settings from block attributes.
'better_payment_form_payment_source' => $payment_source,
'better_payment_form_payment_type' => '',
'better_payment_form_woocommerce_product_id' => $woocommerce_product_id,
'better_payment_form_fluentcart_product_id' => $fluentcart_product_id,
'better_payment_form_payment_source_stripe_price_id' => $stripe_price_id,
// Sidebar settings from block attributes.
'better_payment_form_sidebar_show' => $sidebar_show,
'better_payment_form_transaction_details_heading' => $transaction_title,
'better_payment_form_transaction_details_sub_heading' => $transaction_sub_title,
'better_payment_form_transaction_details_product_title' => $product_title,
'better_payment_form_transaction_details_amount_text' => $amount_text,
// Amount list settings from block attributes.
'better_payment_show_amount_list' => $show_amount_list,
'better_payment_amount' => $amount_list,
// Form fields from block attributes (converted to Elementor format).
'better_payment_form_fields' => $form_fields,
// Button text settings from block attributes.
'better_payment_form_form_buttons_paypal_button_text' => $paypal_button_text,
'better_payment_form_form_buttons_stripe_button_text' => $stripe_button_text,
'better_payment_form_form_buttons_paystack_button_text' => $paystack_button_text,
// Placeholder settings.
'better_payment_placeholder_switch' => 'yes',
// Email notification settings.
'better_payment_form_email_enable' => $email_notification_enabled,
// Admin email settings.
'better_payment_email_to' => $email_to,
'better_payment_email_subject' => $email_subject,
'better_payment_email_content' => $email_content,
'better_payment_email_from' => $email_from,
'better_payment_email_from_name' => $email_from_name,
'better_payment_email_reply_to' => $email_reply_to,
'better_payment_email_cc' => $email_cc,
'better_payment_email_bcc' => $email_bcc,
'better_payment_email_content_type' => $email_content_type,
'better_payment_email_content_heading' => $email_content_heading,
'better_payment_email_content_from_section' => $email_content_from_section,
'better_payment_email_content_to_section' => $email_content_to_section,
'better_payment_email_content_transaction_summary' => $email_content_transaction_summary,
'better_payment_email_content_footer_text' => $email_content_footer_text,
// Customer email settings.
'better_payment_email_subject_customer' => $email_subject_customer,
'better_payment_email_content_customer' => $email_content_customer,
'better_payment_email_from_customer' => $email_from_customer,
'better_payment_email_from_name_customer' => $email_from_name_customer,
'better_payment_email_reply_to_customer' => $email_reply_to_customer,
'better_payment_email_cc_customer' => $email_cc_customer,
'better_payment_email_bcc_customer' => $email_bcc_customer,
'better_payment_email_content_type_customer' => $email_content_type_customer,
'better_payment_form_email_attachment_pdf_show' => $email_attachment_pdf_show,
'better_payment_form_email_attachment' => $email_attachment,
'better_payment_form_email_logo' => $this->build_email_logo_setting( $email_icon, 'bp-icon bp-envelope' ),
// Success message settings from block attributes.
'better_payment_form_success_message_icon' => $this->build_icon_control_setting( $success_icon ),
'better_payment_form_success_message_heading' => $success_heading,
'better_payment_form_success_message_sub_heading' => $success_sub_heading,
'better_payment_form_success_message_transaction' => $transaction_id_text,
'better_payment_form_success_message_thanks' => $thanks_message,
'better_payment_form_success_message_amount_text' => $amount_message,
'better_payment_form_success_message_currency_text' => $currency_message,
'better_payment_form_success_message_pay_method_text' => $payment_method_message,
'better_payment_form_success_message_pay_type_text' => $payment_type_message,
'better_payment_form_success_message_merchant_details_text' => $merchant_details_text,
'better_payment_form_success_message_paid_amount_text' => $paid_amount_text,
'better_payment_form_success_message_purchase_details_text' => $purchase_details_text,
'better_payment_form_success_message_print_btn_text' => $print_btn_text,
'better_payment_form_success_message_view_details_btn_text' => $view_details_btn_text,
'better_payment_form_success_page_view_details_url' => array(
'url' => $user_dashboard_url,
),
'better_payment_form_success_page_url' => array(
'url' => $custom_redirect_url,
),
// Error message settings from block attributes.
'better_payment_form_error_message_icon' => $this->build_icon_control_setting( $error_icon ),
'better_payment_form_error_message_heading' => $error_heading,
'better_payment_form_error_message_sub_heading' => $error_sub_heading,
'better_payment_form_error_message_transaction_id_text' => $error_transaction_id_text,
'better_payment_form_error_details_button_switch' => $error_show_details_button,
'better_payment_form_error_details_button_text' => $error_details_button_text,
'better_payment_form_error_details_page_url' => array(
'url' => $error_details_button_url,
),
'better_payment_form_error_page_url' => array(
'url' => $error_redirect_url,
),
// Flag to identify this form is from Gutenberg block (not Elementor)
'better_payment_form_source' => 'block',
);
}
/**
* Resolve the correct Stripe key based on live mode setting.
*
* This matches Elementor's behavior where the key is resolved before being stored
* in better_payment_stripe_public_key/better_payment_stripe_secret_key.
* Handler::stripe_payment_success() uses better_payment_stripe_secret_key directly.
*
* @param array $global_settings Global settings array.
* @param string $key_type Either 'public' or 'secret'.
* @return string The resolved key.
*/
private function resolve_stripe_key( $global_settings, $key_type ) {
$is_live_mode = ! empty( $global_settings['better_payment_settings_payment_stripe_live_mode'] )
&& 'yes' === $global_settings['better_payment_settings_payment_stripe_live_mode'];
if ( 'public' === $key_type ) {
return $is_live_mode
? ( ! empty( $global_settings['better_payment_settings_payment_stripe_live_public'] ) ? $global_settings['better_payment_settings_payment_stripe_live_public'] : '' )
: ( ! empty( $global_settings['better_payment_settings_payment_stripe_test_public'] ) ? $global_settings['better_payment_settings_payment_stripe_test_public'] : '' );
} else {
return $is_live_mode
? ( ! empty( $global_settings['better_payment_settings_payment_stripe_live_secret'] ) ? $global_settings['better_payment_settings_payment_stripe_live_secret'] : '' )
: ( ! empty( $global_settings['better_payment_settings_payment_stripe_test_secret'] ) ? $global_settings['better_payment_settings_payment_stripe_test_secret'] : '' );
}
}
/**
* Resolve the correct Paystack key based on live mode setting.
*
* This matches Elementor's behavior where the key is resolved before being stored
* in better_payment_paystack_public_key/better_payment_paystack_secret_key.
*
* @param array $global_settings Global settings array.
* @param string $key_type Either 'public' or 'secret'.
* @return string The resolved key.
*/
private function resolve_paystack_key( $global_settings, $key_type ) {
$is_live_mode = ! empty( $global_settings['better_payment_settings_payment_paystack_live_mode'] )
&& 'yes' === $global_settings['better_payment_settings_payment_paystack_live_mode'];
if ( 'public' === $key_type ) {
return $is_live_mode
? ( ! empty( $global_settings['better_payment_settings_payment_paystack_live_public'] ) ? $global_settings['better_payment_settings_payment_paystack_live_public'] : '' )
: ( ! empty( $global_settings['better_payment_settings_payment_paystack_test_public'] ) ? $global_settings['better_payment_settings_payment_paystack_test_public'] : '' );
} else {
return $is_live_mode
? ( ! empty( $global_settings['better_payment_settings_payment_paystack_live_secret'] ) ? $global_settings['better_payment_settings_payment_paystack_live_secret'] : '' )
: ( ! empty( $global_settings['better_payment_settings_payment_paystack_test_secret'] ) ? $global_settings['better_payment_settings_payment_paystack_test_secret'] : '' );
}
}
/**
* Sanitize a comma-separated list of email addresses.
*
* This method validates and sanitizes email addresses to prevent
* email header injection attacks and ensure only valid emails are used.
*
* @param string $email_list Comma-separated list of email addresses.
* @return string Sanitized comma-separated list of valid email addresses.
*/
private function sanitize_email_list( $email_list ) {
if ( empty( $email_list ) ) {
return '';
}
// Split by comma and sanitize each email.
$emails = array_map( 'trim', explode( ',', $email_list ) );
$valid_emails = array();
foreach ( $emails as $email ) {
$sanitized = sanitize_email( $email );
// Only include valid email addresses.
if ( ! empty( $sanitized ) && is_email( $sanitized ) ) {
$valid_emails[] = $sanitized;
}
}
return implode( ', ', $valid_emails );
}
/**
* Convert block formFields array to Elementor widget format.
*
* @param array $attributes Block attributes.
* @return array Converted form fields in Elementor format.
*/
private function convert_form_fields( $attributes ) {
$form_fields = array();
if ( empty( $attributes['formFields'] ) || ! is_array( $attributes['formFields'] ) ) {
// Return default form fields if none provided.
return array(
array(
'_id' => 'first_name_field',
'better_payment_primary_field_type' => 'primary_first_name',
'better_payment_field_name_heading' => __( 'First Name', 'better-payment' ),
'better_payment_field_name_placeholder' => __( 'First Name', 'better-payment' ),
'better_payment_field_type' => 'text',
'better_payment_field_icon' => array( 'value' => 'bp-icon bp-user', 'library' => '' ),
'better_payment_field_name_required' => '',
'better_payment_field_name_show' => 'yes',
),
array(
'_id' => 'last_name_field',
'better_payment_primary_field_type' => 'primary_last_name',
'better_payment_field_name_heading' => __( 'Last Name', 'better-payment' ),
'better_payment_field_name_placeholder' => __( 'Last Name', 'better-payment' ),
'better_payment_field_type' => 'text',
'better_payment_field_icon' => array( 'value' => 'bp-icon bp-user', 'library' => '' ),
'better_payment_field_name_required' => '',
'better_payment_field_name_show' => 'yes',
),
array(
'_id' => 'email_field',
'better_payment_primary_field_type' => 'primary_email',
'better_payment_field_name_heading' => __( 'Email', 'better-payment' ),
'better_payment_field_name_placeholder' => __( 'Email Address', 'better-payment' ),
'better_payment_field_type' => 'email',
'better_payment_field_icon' => array( 'value' => 'bp-icon bp-envelope', 'library' => '' ),
'better_payment_field_name_required' => 'yes',
'better_payment_field_name_show' => 'yes',
),
array(
'_id' => 'amount_field',
'better_payment_primary_field_type' => 'primary_payment_amount',
'better_payment_field_name_heading' => __( 'Amount', 'better-payment' ),
'better_payment_field_name_placeholder' => __( 'Payment Amount', 'better-payment' ),
'better_payment_field_type' => 'number',
'better_payment_field_icon' => array( 'value' => 'bp-icon bp-logo-2', 'library' => '' ),
'better_payment_field_name_required' => 'yes',
'better_payment_field_name_show' => 'yes',
'better_payment_field_name_min' => 1,
'better_payment_field_name_max' => '',
'better_payment_field_name_default' => '',
'better_payment_field_name_default_fixed' => '',
'better_payment_field_name_default_dynamic_enable' => '',
),
);
}
foreach ( $attributes['formFields'] as $index => $field ) {
$raw_icon_value = isset( $field['icon'] ) ? sanitize_text_field( $field['icon'] ) : 'bp-icon bp-user';
$icon_setting = $this->build_icon_control_setting( $raw_icon_value, 'bp-icon bp-user' );
$converted_field = array(
'_id' => 'field_' . $index,
'better_payment_primary_field_type' => isset( $field['primaryFieldType'] ) ? sanitize_text_field( $field['primaryFieldType'] ) : '',
'better_payment_field_name_heading' => isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '',
'better_payment_field_name_placeholder' => isset( $field['placeholder'] ) ? sanitize_text_field( $field['placeholder'] ) : '',
'better_payment_field_type' => isset( $field['type'] ) ? sanitize_text_field( $field['type'] ) : 'text',
'better_payment_field_icon' => array(
'value' => $icon_setting['value'],
'library' => $icon_setting['library'],
),
'better_payment_field_name_required' => isset( $field['required'] ) && $field['required'] ? 'yes' : '',
'better_payment_field_name_show' => isset( $field['show'] ) && $field['show'] ? 'yes' : '',
'better_payment_field_name_display_inline' => isset( $field['displayInline'] ) && $field['displayInline'] ? 'inline-block' : '',
);
// Add amount field specific settings.
if ( isset( $field['primaryFieldType'] ) && 'primary_payment_amount' === $field['primaryFieldType'] ) {
$converted_field['better_payment_field_name_min'] = isset( $field['min'] ) ? intval( $field['min'] ) : 1;
$converted_field['better_payment_field_name_max'] = isset( $field['max'] ) ? intval( $field['max'] ) : '';
$converted_field['better_payment_field_name_default'] = isset( $field['default'] ) ? intval( $field['default'] ) : '';
$converted_field['better_payment_field_name_default_fixed'] = isset( $field['defaultFixed'] ) && $field['defaultFixed'] ? 'yes' : '';
$converted_field['better_payment_field_name_default_dynamic_enable'] = isset( $field['defaultDynamicEnable'] ) && $field['defaultDynamicEnable'] ? 'yes' : '';
}
$form_fields[] = $converted_field;
}
return $form_fields;
}
/**
* Convert block amountList array to Elementor widget format.
*
* @param array $attributes Block attributes.
* @return array Converted amount list in Elementor format.
*/
private function convert_amount_list( $attributes ) {
$amount_list = array();
if ( empty( $attributes['amountList'] ) || ! is_array( $attributes['amountList'] ) ) {
return array();
}
foreach ( $attributes['amountList'] as $index => $item ) {
if ( isset( $item['label'] ) && '' !== $item['label'] ) {
$amount_list[] = array(
'_id' => 'amount_' . $index,
'better_payment_amount_val' => sanitize_text_field( $item['label'] ),
);
}
}
return $amount_list;
}
/**
* Normalize icon classes for rendering compatibility.
*
* Dashicons require the base `dashicons` class plus the icon class.
*
* @param string $icon Icon class string.
* @return string
*/
private function normalize_icon_class( $icon ) {
$icon = trim( (string) $icon );
if ( '' === $icon ) {
return $icon;
}
if ( false !== strpos( $icon, 'dashicons-' ) && false === strpos( $icon, 'dashicons ' ) ) {
$icon = 'dashicons ' . $icon;
}
return $icon;
}
/**
* Convert block icon value to Elementor ICONS control format.
*
* Keeps default state empty-library so existing default notice icons are shown.
*
* @param string $icon_value Icon class or uploaded icon URL.
* @param string $default_value Default icon class for this control.
* @return array
*/
private function build_icon_control_setting( $icon_value, $default_value = '' ) {
$icon_value = trim( (string) $icon_value );
// Only return empty library when icon_value is truly empty (no selection)
// Don't treat icons matching the default as empty - they should render as custom icons
if ( '' === $icon_value ) {
return array(
'value' => '',
'library' => '',
);
}
// Check if the value is an image URL (SVG, PNG, JPG, etc.)
if ( $this->is_icon_url( $icon_value ) ) {
return array(
'value' => array(
'url' => esc_url_raw( $icon_value ),
),
'library' => 'svg',
);
}
// For all other non-empty values (custom CSS classes, FontAwesome, etc.)
// Detect the library type and return it so Handler can render correctly
return array(
'value' => $this->normalize_icon_class( $icon_value ),
'library' => $this->detect_icon_library( $icon_value ),
);
}
/**
* Build email logo setting from block email icon value.
*
* Email template expects a media-like structure with a `url` key.
*
* @param string $icon_value Icon class or uploaded icon URL.
* @param string $default_value Default icon class for this control.
* @return array
*/
private function build_email_logo_setting( $icon_value, $default_value = '' ) {
$icon_value = trim( (string) $icon_value );
if ( '' === $icon_value || ( '' !== $default_value && $icon_value === $default_value ) ) {
return array();
}
if ( $this->is_icon_url( $icon_value ) ) {
return array(
'url' => esc_url_raw( $icon_value ),
);
}
return array();
}
/**
* Detect Elementor-style icon library key from icon class string.
*
* @param string $icon_value Icon class.
* @return string
*/
private function detect_icon_library( $icon_value ) {
$icon_value = (string) $icon_value;
// Check for FontAwesome icons (has 'fa-' or 'fa ' prefix)
if ( false !== strpos( $icon_value, 'fa-' ) || false !== strpos( $icon_value, 'fa ' ) ) {
// Check for specific FontAwesome variants (fas, far, fab, fal, fad)
if ( preg_match( '/\bfab\b/', $icon_value ) ) {
return 'fab'; // FontAwesome Brands
}
if ( preg_match( '/\bfar\b/', $icon_value ) ) {
return 'far'; // FontAwesome Regular
}
if ( preg_match( '/\bfal\b/', $icon_value ) ) {
return 'fal'; // FontAwesome Light
}
if ( preg_match( '/\bfad\b/', $icon_value ) ) {
return 'fad'; // FontAwesome Duotone
}
// Default to FontAwesome Solid
return 'fas';
}
// Check for Dashicons
if ( false !== strpos( $icon_value, 'dashicon' ) ) {
return 'dashicons';
}
// Any other CSS class (custom icons like 'bp-icon bp-check-circle')
return 'custom';
}
/**
* Check whether an icon value is a URL-like image path.
*
* @param string $icon_value Icon value.
* @return bool
*/
private function is_icon_url( $icon_value ) {
$icon_value = trim( (string) $icon_value );
if ( '' === $icon_value ) {
return false;
}
return 0 === strpos( $icon_value, 'http://' )
|| 0 === strpos( $icon_value, 'https://' )
|| 0 === strpos( $icon_value, '/' )
|| 0 === strpos( $icon_value, 'data:image/' );
}
/**
* Create a widget proxy object for layout templates.
*
* This provides the methods that layout files expect from $widgetObj and $this.
*
* @param string $block_id The unique block ID.
* @param array $settings The settings array.
* @return object Widget proxy object with required methods.
*/
private function create_widget_proxy( $block_id, $settings ) {
return new class( $block_id, $settings ) {
/**
* Block ID.
*
* @var string
*/
private $id;
/**
* Settings array.
*
* @var array
*/
private $settings;
/**
* Constructor.
*
* @param string $id Block ID.
* @param array $settings Settings array.
*/
public function __construct( $id, $settings ) {
$this->id = $id;
$this->settings = $settings;
}
/**
* Get the block ID.
*
* @return string Block ID.
*/
public function get_id() {
return $this->id;
}
/**
* Get default payment amount text.
*
* @param array $settings Settings array.
* @return string Default amount text.
*/
public function render_attribute_default_text( $settings ) {
$render_attribute_default_text = '';
$items = ! empty( $settings['better_payment_form_fields'] ) ? $settings['better_payment_form_fields'] : array();
foreach ( $items as $item ) {
// Check for primary_payment_amount field type.
if ( ! empty( $item['better_payment_primary_field_type'] ) && 'primary_payment_amount' === $item['better_payment_primary_field_type'] ) {
$render_attribute_default_text = ! empty( $item['better_payment_field_name_default'] )
? $item['better_payment_field_name_default']
: '';
break;
}
}
return $render_attribute_default_text;
}
/**
* Render amount selection element.
*
* @param array $settings Settings array.
* @param array $args Additional arguments.
* @return void
*/
public function render_amount_element( $settings, $args = array() ) {
if ( empty( $settings['better_payment_amount'] ) ) {
return;
}
$items = $settings['better_payment_amount'];
// Get currency symbol and alignment for display.
$bp_helper_obj = new \Better_Payment\Lite\Classes\Helper();
$currency = ! empty( $settings['better_payment_form_currency'] ) ? $settings['better_payment_form_currency'] : 'USD';
$currency_symbol = $bp_helper_obj->get_currency_symbol( esc_html( $currency ) );
$currency_alignment = ! empty( $settings['better_payment_form_currency_alignment'] ) ? $settings['better_payment_form_currency_alignment'] : 'left';
$currency_left = 'left' === $currency_alignment ? $currency_symbol : '';
$currency_right = 'right' === $currency_alignment ? $currency_symbol : '';
foreach ( $items as $item ) :
if ( ! empty( $item['better_payment_amount_val'] ) ) :
$uid = uniqid();
$value = floatval( $item['better_payment_amount_val'] );
?>
';
}
};
}
/**
* Enqueue editor assets (controls library).
*
* @return void
*/
public function enqueue_editor_assets() {
// Only load on block editor pages
$screen = get_current_screen();
if ( ! $screen || ! $screen->is_block_editor() ) {
return;
}
global $pagenow;
$editor_type = false;
if ( $pagenow === 'post-new.php' || $pagenow === 'post.php' ) {
$editor_type = 'edit-post';
} elseif ( $pagenow === 'site-editor.php' || ( $pagenow === 'themes.php' && isset( $_GET[ 'page' ] ) && sanitize_text_field( wp_unslash( $_GET[ 'page' ] ) ) === 'gutenberg-edit-site' ) ) {
$editor_type = 'edit-site';
} elseif ( $pagenow === 'widgets.php' ) {
$editor_type = 'edit-widgets';
}
// Define EssentialBlocksLocalize before controls load (controls access it at module level)
$localize_data = array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'better_payment_block_nonce' ),
'admin_nonce' => wp_create_nonce( 'better_payment_admin_nonce' ),
'rest_rootURL' => esc_url_raw( rest_url() ),
'is_pro_active' => 'false',
'eb_plugins_url' => BETTER_PAYMENT_URL . '/',
'image_url' => BETTER_PAYMENT_ASSETS . '/img',
'fontAwesome' => 'true',
'googleFont' => 'true',
'quickToolbar' => 'false',
'enableGenerateImage' => '0',
'enableWriteAIInputField' => '0',
'enableWriteAIRichtext' => '0',
'enableRewriteAIContent' => '0',
'hasOpenAiApiKey' => '0',
'all_blocks_default' => array(),
'all_blocks' => array(),
'quick_toolbar_blocks' => array(),
'responsiveBreakpoints' => array(
'tablet' => 1024,
'mobile' => 767,
),
);
// Add inline script to wp-editor which is guaranteed to load in block editor
wp_add_inline_script(
'wp-editor',
'window.EssentialBlocksLocalize = window.EssentialBlocksLocalize || ' . wp_json_encode( $localize_data ) . ';'
);
// Enqueue necessary CSS files for form styles in editor (used in render.php)
wp_enqueue_style( 'better-payment-el' );
wp_enqueue_style( 'bp-icon-front' );
wp_enqueue_style( 'better-payment-style' );
wp_enqueue_style( 'better-payment-common-style' );
wp_enqueue_style( 'better-payment-admin-style' );
wp_enqueue_style( 'bp-dashboard-pagination-style' );
$this->enqueue_font_awesome();
wp_enqueue_style( 'dashicons' );
// Build block data: start with common keys, then merge each block's own data.
// To add data for a new block, implement get_{block-key}_editor_data() and add
// 'editor_data' => 'get_{block-key}_editor_data' to the $blocks registry above.
$block_data = $this->get_common_editor_data();
foreach ( $this->blocks as $block_config ) {
if ( ! empty( $block_config['editor_data'] ) && method_exists( $this, $block_config['editor_data'] ) ) {
$block_data = array_merge( $block_data, $this->{$block_config['editor_data']}() );
}
}
// Canonical filter for the full merged data; the legacy alias preserves backwards
// compatibility with any pro-plugin hooks on the old per-block filter name.
$block_data = apply_filters( 'better_payment/block/editor_block_data', $block_data );
$block_data = apply_filters( 'better_payment/block/user_dashboard/editor_block_data', $block_data );
wp_add_inline_script(
'wp-editor',
'window.betterPaymentBlockData = ' . wp_json_encode( $block_data ) . ';'
);
wp_add_inline_script(
'wp-editor',
'window.eb_conditional_localize = ' . wp_json_encode(
$editor_type !== false ? [ 'editor_type' => $editor_type ] : []
) . ';'
);
}
/**
* Enqueue frontend assets for blocks
* Called on wp_enqueue_scripts hook to allow pro plugin to enqueue scripts early
*
* @return void
*/
public function enqueue_frontend_assets() {
// Allow pro plugin to enqueue scripts for the user dashboard block
do_action( 'better_payment/block/user_dashboard/wp_enqueue_scripts' );
}
/**
* Return the site domain (host only, no protocol) for use in editor data.
*
* @return string
*/
private function get_site_domain() {
$parsed = wp_parse_url( get_site_url() );
return ! empty( $parsed['host'] ) ? esc_html( $parsed['host'] ) : 'example.com';
}
/**
* Return plugin settings with all gateway secret/private keys removed.
*
* Safe to expose to any block editor user regardless of role. Only the keys
* that the editor UI actually reads (email defaults, gateway enable flags,
* public keys) are kept — secrets never leave the server.
*
* @return array
*/
private function get_safe_settings_for_editor() {
$settings = get_option( 'better_payment_settings', array() );
if ( ! is_array( $settings ) ) {
return array();
}
$sensitive_keys = array(
'better_payment_settings_payment_paypal_live_secret',
'better_payment_settings_payment_paypal_test_secret',
'better_payment_settings_payment_stripe_live_secret',
'better_payment_settings_payment_stripe_test_secret',
'better_payment_settings_payment_paystack_live_secret',
'better_payment_settings_payment_paystack_test_secret',
);
foreach ( $sensitive_keys as $key ) {
unset( $settings[ $key ] );
}
return $settings;
}
/**
* Return data shared by every registered block in the editor.
*
* All blocks can read these keys from window.betterPaymentBlockData.
* Adding a new block? Common data lives here; block-specific data goes in
* get_{block-key}_editor_data() and is registered via $blocks['editor_data'].
*
* @return array
*/
private function get_common_editor_data() {
return array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'better_payment_block_nonce' ),
'currencies' => $this->get_currency_list(),
'assetUrl' => BETTER_PAYMENT_ASSETS,
'assetsUrl' => BETTER_PAYMENT_ASSETS,
'siteName' => get_bloginfo( 'name' ),
'siteDomain' => $this->get_site_domain(),
'proEnabled' => apply_filters( 'better_payment/pro_enabled', false ),
);
}
/**
* Return editor data specific to the payment-form block.
*
* Provides the global settings (secrets stripped) so the inspector can
* pre-populate email defaults, and the site admin email for the To field.
*
* @return array
*/
private function get_payment_form_editor_data() {
return array(
'betterPaymentSettings' => $this->get_safe_settings_for_editor(),
'adminEmail' => sanitize_email( get_option( 'admin_email' ) ),
);
}
/**
* Return editor data specific to the user-dashboard block.
*
* Fetches the current editor user's transactions, subscriptions, and analytics
* so the block preview shows real data immediately on insertion without a REST
* round-trip. Normalises each row to the same flat shape the UserAPI returns so
* the JS preview component only needs to handle one structure.
*
* @return array
*/
private function get_user_dashboard_editor_data() {
$current_user = wp_get_current_user();
// Cache the 5 DB queries for the duration of the browser session in the editor.
// The cache is keyed per user so each editor sees only their own data.
// TTL: 5 minutes — short enough to reflect recent transactions without hammering
// the DB on every page navigation inside the block editor.
$cache_key = 'bp_editor_dash_' . ( $current_user->ID ?? 0 );
$cache_group = 'better_payment';
$cached = wp_cache_get( $cache_key, $cache_group );
if ( false !== $cached ) {
// Nonces are request-scoped and must never be cached; regenerate on every hit.
$cached['restNonce'] = wp_create_nonce( 'wp_rest' );
return $cached;
}
$user_transactions = array();
$user_subscriptions = array();
$user_analytics = array();
$user_transactions_meta = array( 'total' => 0, 'pages' => 1, 'perPage' => 20 );
// Safe default ensures userSubscriptionsMeta is always fully defined even when
// the user has no subscriptions or is not logged in.
$sub_result = array( 'total' => 0, 'pages' => 1, 'per_page' => 20, 'transactions' => array() );
// Always fetch in the editor — has_block() only checks saved content and returns
// false before first save, leaving the block with empty data on insertion.
if ( $current_user && $current_user->ID ) {
$user_analytics = \Better_Payment\Lite\Admin\DB::get_user_analytics_by_email( $current_user->user_email );
$paged_result = \Better_Payment\Lite\Admin\DB::get_user_transactions_paginated(
$current_user->user_email,
array(
'page' => 1,
'per_page' => 20,
'type' => 'transactions',
)
);
$raw_transactions = $paged_result['transactions'];
$user_transactions_meta = array(
'total' => $paged_result['total'],
'pages' => $paged_result['pages'],
'perPage' => $paged_result['per_page'],
);
// Unserialize and normalise to match UserAPI REST response format.
foreach ( $raw_transactions as $tx ) {
if ( ! empty( $tx->form_fields_info ) ) {
$tx->form_fields_info = maybe_unserialize( $tx->form_fields_info );
}
if ( ! empty( $tx->customer_info ) ) {
$tx->customer_info = maybe_unserialize( $tx->customer_info );
}
$ffi = is_array( $tx->form_fields_info ) ? $tx->form_fields_info : array();
$customer_name = isset( $ffi['primary_first_name'] )
? trim( sanitize_text_field( $ffi['primary_first_name'] ) . ' ' . sanitize_text_field( $ffi['primary_last_name'] ?? '' ) )
: '';
if ( empty( $customer_name ) ) {
$customer_name = isset( $ffi['first_name'] )
? trim( sanitize_text_field( $ffi['first_name'] ) . ' ' . sanitize_text_field( $ffi['last_name'] ?? '' ) )
: '';
}
$tx->customer_name = $customer_name;
$tx->customer_email = sanitize_text_field( $ffi['primary_email'] ?? ( $ffi['email'] ?? '' ) );
}
$user_transactions = apply_filters( 'better_payment/block/user_dashboard/enrich_user_transactions', $raw_transactions );
// Separate query for subscriptions (not a subset of transactions page 1).
$sub_result = \Better_Payment\Lite\Admin\DB::get_user_transactions_paginated(
$current_user->user_email,
array(
'page' => 1,
'per_page' => 20,
'type' => 'subscriptions',
)
);
$raw_subscriptions = $sub_result['transactions'];
foreach ( $raw_subscriptions as $tx ) {
if ( ! empty( $tx->form_fields_info ) ) {
$tx->form_fields_info = maybe_unserialize( $tx->form_fields_info );
}
}
$user_subscriptions = apply_filters( 'better_payment/block/user_dashboard/enrich_user_subscriptions', $raw_subscriptions );
// Flatten each subscription to match UserAPI flat format so JS handles one structure.
foreach ( $user_subscriptions as $tx ) {
$ffi = is_array( $tx->form_fields_info ) ? $tx->form_fields_info : array();
$tx->subscription_id = sanitize_text_field( $ffi['subscription_id'] ?? '' );
$tx->subscription_plan_id = sanitize_text_field( $ffi['subscription_plan_id'] ?? '' );
$tx->subscription_status = sanitize_text_field( $ffi['subscription_status'] ?? '' );
$tx->subscription_interval = sanitize_text_field( $ffi['subscription_interval'] ?? '' );
$tx->subscription_created_date = intval( $ffi['subscription_created_date'] ?? 0 );
$tx->subscription_current_period_end = intval( $ffi['subscription_current_period_end'] ?? 0 );
$tx->is_payment_split_payment = ! empty( $ffi['is_payment_split_payment'] ) ? intval( $ffi['is_payment_split_payment'] ) : 0;
$tx->subscription_product_name = sanitize_text_field( $ffi['subscription_product_name'] ?? '' );
$tx->is_subscription = ! empty( $ffi['subscription_id'] ) ? 'Subscription' : 'One Time';
$customer_name = isset( $ffi['primary_first_name'] )
? trim( sanitize_text_field( $ffi['primary_first_name'] ) . ' ' . sanitize_text_field( $ffi['primary_last_name'] ?? '' ) )
: '';
if ( empty( $customer_name ) ) {
$customer_name = isset( $ffi['first_name'] )
? trim( sanitize_text_field( $ffi['first_name'] ) . ' ' . sanitize_text_field( $ffi['last_name'] ?? '' ) )
: '';
}
$tx->customer_name = $customer_name;
$tx->customer_email = sanitize_text_field( $ffi['primary_email'] ?? ( $ffi['email'] ?? '' ) );
unset( $tx->form_fields_info );
}
}
$data = array(
'restNonce' => wp_create_nonce( 'wp_rest' ),
'restUrl' => get_rest_url( null, 'better-payment/v1/user-transactions' ),
'currentUser' => array(
'id' => $current_user->ID,
'user_email' => $current_user->user_email,
'user_login' => $current_user->user_login,
'user_avatar_url'=> get_avatar_url( $current_user->user_email, array( 'size' => 32 ) ),
'email' => $current_user->user_email,
'login' => $current_user->user_login,
'avatar' => get_avatar_url( $current_user->user_email, array( 'size' => 32 ) ),
),
'userTransactions' => $user_transactions,
'userTransactionsMeta' => $user_transactions_meta,
'userSubscriptions' => $user_subscriptions,
'userSubscriptionsMeta' => array(
'total' => (int) $sub_result['total'],
'pages' => (int) $sub_result['pages'],
'perPage' => 20,
),
'userAnalytics' => $user_analytics,
'proAssets' => array(
'analyticsReportsBanner' => BETTER_PAYMENT_ASSETS . '/img/user-dashboard-analytics-reports-pro-banner.png',
'recurringSubscriptionBanner' => BETTER_PAYMENT_ASSETS . '/img/user-dashboard-recurring-subscription-pro-banner.png',
'splitSubscriptionBanner' => BETTER_PAYMENT_ASSETS . '/img/user-dashboard-split-subscription-pro-banner.png',
'subscriptionProBanner' => BETTER_PAYMENT_ASSETS . '/img/user-dashboard-subscription-pro-banner.png',
),
);
// Store in the object cache for 5 minutes. nonce is excluded from the cached payload
// because nonces are request-scoped — storing them would return stale nonces on cache hit.
// The restNonce above is regenerated fresh on every request (not cached).
$cacheable = $data;
unset( $cacheable['restNonce'] );
wp_cache_set( $cache_key, $cacheable, $cache_group, 5 * MINUTE_IN_SECONDS );
return $data;
}
/**
* Get the current editor type.
*
* @return string
*/
private function get_editor_type() {
if ( function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
if ( $screen && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
return 'edit-post';
}
}
return 'edit-post';
}
/**
* Register the Better Payment block category.
*
* @param array $categories Block categories.
* @param WP_Block_Editor_Context $context Block editor context.
* @return array Modified block categories.
*/
public function register_block_category( $categories, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
// Check if our category already exists
foreach ( $categories as $category ) {
if ( 'better-payment' === $category['slug'] ) {
return $categories;
}
}
// Add our category at the beginning
array_unshift(
$categories,
array(
'slug' => 'better-payment',
'title' => __( 'Better Payment', 'better-payment' ),
'icon' => '',
)
);
return $categories;
}
/**
* Register all blocks.
*
* @return void
*/
public function register_blocks() {
if ( ! function_exists( 'register_block_type' ) ) {
return;
}
foreach ( $this->blocks as $block_key => $block_config ) {
if ( ! $this->is_block_registered( $block_config['name'] ) ) {
$this->register_single_block( $block_key, $block_config );
}
}
}
/**
* Register a single block.
*
* @param string $block_key Block key.
* @param array $block_config Block configuration.
* @return void
*/
private function register_single_block( $block_key, $block_config ) {
// Use assets/blocks path for block.json since src/ is excluded from production builds.
// The block.json is copied to assets/blocks/ during the build process.
$block_json_path = BETTER_PAYMENT_PATH . '/assets/blocks/blocks/' . $block_config['path'] . '/block.json';
// Fallback to src/blocks for development environment (assets may not be built yet)
if ( ! file_exists( $block_json_path ) ) {
$block_json_path = BETTER_PAYMENT_PATH . '/src/blocks/' . $block_config['path'] . '/block.json';
}
// Check if block.json exists
if ( ! file_exists( $block_json_path ) ) {
return;
}
// Get asset file for dependencies
$asset_file = $this->get_asset_file( $block_config['path'] );
// Add controls as a dependency
$dependencies = array_merge( $asset_file['dependencies'], array( 'better-payment-controls' ) );
// Register editor script
$editor_script_handle = 'better-payment-' . $block_key . '-editor';
if ( file_exists( BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_config['path'] . '/index.min.js' ) ) {
wp_register_script(
$editor_script_handle,
BETTER_PAYMENT_ASSETS . '/blocks/' . $block_config['path'] . '/index.min.js',
$dependencies,
$asset_file['version'],
true
);
} elseif ( file_exists( BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_config['path'] . '/index.js' ) ) {
wp_register_script(
$editor_script_handle,
BETTER_PAYMENT_ASSETS . '/blocks/' . $block_config['path'] . '/index.js',
$dependencies,
$asset_file['version'],
true
);
}
// Register frontend/editor style
$style_handle = 'better-payment-' . $block_key . '-style';
$style_dependencies = array(
'better-payment-controls-style',
'better-payment-el',
'bp-icon-front',
'better-payment-style',
'better-payment-common-style',
'better-payment-admin-style',
'dashicons',
);
if ( ! $this->is_font_awesome_provided() ) {
$style_dependencies[] = 'bp-font-awesome';
}
if ( file_exists( BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_config['path'] . '/style.min.css' ) ) {
wp_register_style(
$style_handle,
BETTER_PAYMENT_ASSETS . '/blocks/' . $block_config['path'] . '/style.min.css',
$style_dependencies,
$asset_file['version']
);
} elseif ( file_exists( BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_config['path'] . '/style.css' ) ) {
wp_register_style(
$style_handle,
BETTER_PAYMENT_ASSETS . '/blocks/' . $block_config['path'] . '/style.css',
$style_dependencies,
$asset_file['version']
);
}
// Build block registration args
$block_args = array(
'editor_script' => $editor_script_handle,
'style' => $style_handle,
);
// Add render callback if defined in block config
if ( ! empty( $block_config['render_callback'] ) && method_exists( $this, $block_config['render_callback'] ) ) {
$block_args['render_callback'] = array( $this, $block_config['render_callback'] );
}
// Register block with WordPress
$result = register_block_type( $block_json_path, $block_args );
if ( false === $result ) {
// Log error in debug mode
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'Better Payment: Failed to register block ' . $block_config['name'] );
}
}
}
/**
* Get asset file with dependencies and version.
*
* @param string $block_path Block path.
* @return array Asset file data.
*/
private function get_asset_file( $block_path ) {
$asset_file_path = BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_path . '/index.min.asset.php';
if ( ! file_exists( $asset_file_path ) ) {
$asset_file_path = BETTER_PAYMENT_PATH . '/assets/blocks/' . $block_path . '/index.asset.php';
}
if ( file_exists( $asset_file_path ) ) {
return include $asset_file_path;
}
return array(
'dependencies' => array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' ),
'version' => BETTER_PAYMENT_VERSION,
);
}
/**
* Get the blocks directory path.
*
* @return string
*/
public function get_blocks_path() {
return BETTER_PAYMENT_PATH . '/src/blocks/';
}
/**
* Get the blocks assets URL.
*
* @return string
*/
public function get_blocks_url() {
return BETTER_PAYMENT_ASSETS . '/blocks/';
}
/**
* Check if a block is registered.
*
* @param string $block_name Full block name (e.g., 'better-payment/payment-form').
* @return bool
*/
public function is_block_registered( $block_name ) {
return \WP_Block_Type_Registry::get_instance()->is_registered( $block_name );
}
/**
* Check if Font Awesome is already provided by another plugin (e.g. Elementor).
*
* @return bool
*/
private function is_font_awesome_provided() {
$handles = [
'font-awesome-5-all',
'font-awesome-6-all',
'elementor-icons-fa-solid',
'elementor-icons-fa-brands',
'elementor-icons-fa-regular',
];
foreach ( $handles as $handle ) {
if ( wp_style_is( $handle, 'registered' ) || wp_style_is( $handle, 'enqueued' ) ) {
return true;
}
}
return false;
}
/**
* Enqueue Font Awesome only if not already provided by another plugin.
*
* @return void
*/
private function enqueue_font_awesome() {
if ( ! $this->is_font_awesome_provided() ) {
wp_enqueue_style( 'bp-font-awesome' );
}
}
}