2024-05-06 11:04:37 +07:00

369 lines
11 KiB
PHP

<?php
namespace ElementorPro\Modules\Payments\Widgets;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use Elementor\Plugin;
use Elementor\Utils;
use ElementorPro\Modules\Payments\Classes\Payment_Button;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Paypal_Button extends Payment_Button {
// API integration types.
const API_TYPE_SIMPLE = 'simple';
const API_TYPE_ADVANCED = 'advanced';
// PayPal constants.
const PROD_URL = 'https://www.paypal.com/cgi-bin/webscr';
const SANDBOX_URL = 'https://sandbox.paypal.com/cgi-bin/webscr';
const CMD_CHECKOUT = '_xclick';
const CMD_DONATION = '_donations';
const CMD_SUBSCRIPTION = '_xclick-subscriptions';
const BILLING_CYCLE_TYPES = [
self::BILLING_CYCLE_DAYS => 'D',
self::BILLING_CYCLE_WEEKS => 'W',
self::BILLING_CYCLE_MONTHS => 'M',
self::BILLING_CYCLE_YEARS => 'Y',
];
public function get_name() {
return 'paypal-button';
}
public function get_title() {
return esc_html__( 'PayPal Button', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-paypal-button';
}
public function get_keywords() {
return [ 'paypal', 'payment', 'sell', 'donate' ];
}
protected function get_merchant_name() {
return 'PayPal';
}
// Retrieve a numerical field from settings, and default to $min if it's too small.
protected function get_numeric_setting( $key, $min = 0 ) {
$num = doubleval( $this->get_settings_for_display( $key ) );
return ( $min > $num ) ? $min : $num;
}
// Print a numerical field from settings, using `get_numeric_setting`.
protected function print_numeric_setting( $key, $min = 0 ) {
// PHPCS - the get_numeric_setting function is safe.
echo $this->get_numeric_setting( $key, $min ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// Get the currently selected API communication method ( legacy / SDK ).
protected function get_api_method() {
$settings = $this->get_settings_for_display();
return ( self::API_TYPE_ADVANCED === $settings['merchant_account'] ) ? 'sdk' : 'legacy';
}
// Get validation errors.
protected function get_errors( $squash_errors = true ) {
$settings = $this->get_settings_for_display();
$errors = [];
// Don't render errors in the editor.
if ( Plugin::instance()->editor->is_edit_mode() ) {
return $errors;
}
// No payment method provided.
if ( 'legacy' === $this->get_api_method() ) {
$empty_email = empty( $settings['email'] );
$empty_sandbox_email = $this->is_sandbox() && empty( $settings['sandbox_email'] );
if ( $empty_email || $empty_sandbox_email ) {
$errors[ self::ERROR_MESSAGE_PAYMENT_METHOD ] = $this->get_custom_message( self::ERROR_MESSAGE_PAYMENT_METHOD );
}
}
// Other errors.
$empty_product_price = ( self::PAYMENT_TYPE_DONATION !== $settings['type'] && empty( $settings['product_price'] ) );
$empty_donation_amount = ( self::DONATION_TYPE_FIXED === $settings['donation_type'] && empty( $settings['donation_amount'] ) );
$empty_tax = ( ! empty( $settings['tax_type'] ) && empty( $settings['tax_rate'] ) );
if ( $empty_product_price || $empty_donation_amount || $empty_tax ) {
$errors[ self::ERROR_MESSAGE_GLOBAL ] = $this->get_custom_message( self::ERROR_MESSAGE_GLOBAL );
}
// Squash errors to show only a global error.
if ( $squash_errors && 1 < count( $errors ) ) {
return [
self::ERROR_MESSAGE_GLOBAL => $this->get_custom_message( self::ERROR_MESSAGE_GLOBAL ),
];
}
return $errors;
}
// Render PayPal's legacy checkout form.
protected function render_legacy_form() {
$settings = $this->get_settings_for_display();
// Handle sandbox mode.
if ( ! $this->is_sandbox() ) {
$form_action = self::PROD_URL;
$email = $settings['email'];
} else {
$form_action = self::SANDBOX_URL;
$email = $settings['sandbox_email'];
$this->add_render_attribute( 'button', 'class', 'elementor-payment-sandbox-mode' );
}
if ( 'yes' === $settings['open_in_new_window'] ) {
$target = '_blank';
} else {
$target = '_top';
}
// Set PayPal payment settings by payment type.
switch ( $settings['type'] ) {
case self::PAYMENT_TYPE_CHECKOUT:
$cmd = self::CMD_CHECKOUT;
$price_field = [
'name' => 'amount',
'value' => $settings['product_price'],
];
break;
case self::PAYMENT_TYPE_DONATION:
$cmd = self::CMD_DONATION;
$donation_amount = '';
// phpcs:ignore
if ( self::DONATION_TYPE_FIXED === $settings['donation_type'] ) {
$donation_amount = $settings['donation_amount'];
}
$price_field = [
'name' => 'amount',
'value' => $donation_amount,
];
break;
case self::PAYMENT_TYPE_SUBSCRIPTION:
$cmd = self::CMD_SUBSCRIPTION;
$price_field = [
'name' => 'a3',
'value' => $settings['product_price'],
];
$auto_renewal = ( 'yes' === $settings['auto_renewal'] ) ? 1 : 0;
$billing_cycle = self::BILLING_CYCLE_TYPES[ $settings['billing_cycle'] ];
break;
}
// PayPal HTML reference:
// https://developer.paypal.com/docs/paypal-payments-standard/integration-guide/html-reference-landing/
?>
<form action="<?php echo esc_attr( $form_action ); ?>" method="post" target="<?php echo esc_attr( $target ); ?>">
<input type="hidden" name="cmd" value="<?php echo esc_attr( $cmd ); ?>" />
<input type="hidden" name="business" value="<?php echo esc_attr( $email ); ?>" />
<input type="hidden" name="lc" value="US" />
<input type="hidden" name="item_name" value="<?php echo esc_attr( $settings['product_name'] ); ?>" />
<input type="hidden" name="item_number" value="<?php echo esc_attr( $settings['product_sku'] ); ?>" />
<input type="hidden" name="currency_code" value="<?php echo esc_attr( $settings['currency'] ); ?>" />
<input type="hidden" name="<?php echo esc_attr( $price_field['name'] ); ?>" value="<?php echo esc_attr( $price_field['value'] ); ?>" />
<input type="hidden" name="no_note" value="1">
<?php if ( self::PAYMENT_TYPE_CHECKOUT === $settings['type'] ) { ?>
<input type="hidden" name="shipping" value="<?php $this->print_numeric_setting( 'shipping_price' ); ?>" />
<input type="hidden" name="tax_rate" value="<?php $this->print_numeric_setting( 'tax_rate' ); ?>" />
<input type="hidden" name="quantity" value="<?php $this->print_numeric_setting( 'quantity', 1 ); ?>" />
<?php
} elseif ( self::PAYMENT_TYPE_SUBSCRIPTION === $settings['type'] ) { ?>
<?php // PHPCS - the $auto_renewal variable is a safe. ?>
<input type="hidden" name="src" value="<?php echo $auto_renewal; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" />
<input type="hidden" name="p3" value="1" />
<?php // PHPCS - the $billing_cycle variable is a constant value from self::BILLING_CYCLE_TYPES. ?>
<input type="hidden" name="t3" value="<?php echo $billing_cycle; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" />
<input type="hidden" name="no-shipping" value="1" />
<?php
}
if ( ! empty( $settings['redirect_after_success']['url'] ) ) { ?>
<input type="hidden" name="return" value="<?php echo esc_url( $settings['redirect_after_success']['url'] ); ?>">
<?php
}
$this->add_render_attribute( 'button', 'type', 'submit' );
$this->add_render_attribute( 'button', 'class', 'elementor-paypal-legacy' );
parent::render_button( null, 'button' );
foreach ( $this->get_errors() as $type => $message ) {
?>
<div class="elementor-message elementor-message-danger elementor-hidden elementor-error-message-<?php Utils::print_unescaped_internal_string( $type ); ?>">
<?php echo esc_html( $message ); ?>
</div>
<?php
}
?>
</form>
<?php
}
// Render the payment button.
protected function render_button( Widget_Base $instance = null, $tag = 'a' ) {
switch ( $this->get_api_method() ) {
case 'legacy':
$this->render_legacy_form();
break;
}
}
// Account details section.
protected function register_account_section() {
$this->start_controls_section(
'section_account',
[
'label' => esc_html__( 'Pricing & Payments', 'elementor-pro' ),
]
);
$this->add_control(
'merchant_account',
[
'label' => esc_html__( 'Merchant Account', 'elementor-pro' ),
'type' => Controls_Manager::HIDDEN,
'default' => self::API_TYPE_SIMPLE,
'options' => [
self::API_TYPE_SIMPLE => esc_html__( 'Default (Simple)', 'elementor-pro' ),
self::API_TYPE_ADVANCED => esc_html__( 'Custom (Advanced)', 'elementor-pro' ),
],
'frontend_available' => true,
]
);
$this->add_control(
'email',
[
'label' => esc_html__( 'PayPal Account', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
'description' => esc_html__( 'Transactions made through your PayPal button will be registered under this account.', 'elementor-pro' ),
'label_block' => true,
'condition' => [
'merchant_account' => self::API_TYPE_SIMPLE,
],
'placeholder' => 'yours@email.com',
]
);
$this->add_control(
'sdk_token',
[
'label' => esc_html__( 'SDK Token', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
'label_block' => true,
'condition' => [
'merchant_account' => self::API_TYPE_ADVANCED,
],
]
);
$this->register_product_controls();
$this->end_controls_section();
}
/**
* Updates Button tab controls in 'Style' tab
*
* @since 3.7.0
*/
public function register_paypal_button_controls() {
parent::register_controls();
$this->update_control( 'selected_icon', [
'default' => [
'value' => 'fab fa-paypal',
'library' => 'fa-brands',
],
] );
$this->update_control( 'background_color', [
'default' => '#032E82',
] );
}
/**
* Edit button control initial UI
*
* @since 3.7.0
*
*/
protected function register_controls() {
$this->register_paypal_button_controls();
}
// Custom sandbox controls.
protected function register_sandbox_controls() {
$this->add_control(
'sandbox_email',
[
'label' => esc_html__( 'Sandbox Email Account', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
'description' => esc_html__( 'This is the address given to you by PayPal when you set up a sandbox with your developer account. You can use the sandbox to test your purchase flow.', 'elementor-pro' ),
'label_block' => true,
'condition' => [
'sandbox_mode' => 'yes',
],
]
);
}
// This widget extends the button core widget and therefore needs to overwrite the widget-base core CSS config.
public function get_css_config() {
$widget_name = 'payments';
$direction = is_rtl() ? '-rtl' : '';
$css_file_path = 'css/widget-' . $widget_name . $direction . '.min.css';
/*
* Currently this widget does not support custom-breakpoints in its CSS file.
* In order to support it, this widget needs to get the CSS config from the base-widget-trait.php.
* But to make sure that it implements the Pro assets-path due to the fact that it extends a Core widget.
*/
return [
'key' => $widget_name,
'version' => ELEMENTOR_PRO_VERSION,
'file_path' => ELEMENTOR_PRO_ASSETS_PATH . $css_file_path,
'data' => [
'file_url' => ELEMENTOR_PRO_ASSETS_URL . $css_file_path,
],
];
}
}