<?php
namespace ElementorPro\Modules\Payments\Widgets;
use Elementor\Controls_Manager;
use Elementor\Plugin;
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 __( 'PayPal Button', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-paypal-button';
}
public function get_keywords() {
return [ 'paypal', 'payment', 'sell', 'donate' ];
}
public function get_categories() {
return [ 'pro-elements' ];
}
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;
}
// 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 $form_action; ?>" method="post" target="<?php echo $target; ?>">
<input type="hidden" name="cmd" value="<?php echo $cmd; ?>" />
<input type="hidden" name="business" value="<?php echo $email; ?>" />
<input type="hidden" name="lc" value="US" />
<input type="hidden" name="item_name" value="<?php echo $settings['product_name']; ?>" />
<input type="hidden" name="item_number" value="<?php echo $settings['product_sku']; ?>" />
<input type="hidden" name="currency_code" value="<?php echo $settings['currency']; ?>" />
<input type="hidden" name="<?php echo $price_field['name']; ?>" value="<?php echo $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 echo $this->get_numeric_setting( 'shipping_price' ); ?>" />
<input type="hidden" name="tax_rate" value="<?php echo $this->get_numeric_setting( 'tax_rate' ); ?>" />
<input type="hidden" name="quantity" value="<?php echo $this->get_numeric_setting( 'quantity', 1 ); ?>" />
<?php
} elseif ( self::PAYMENT_TYPE_SUBSCRIPTION === $settings['type'] ) { ?>
<input type="hidden" name="src" value="<?php echo $auto_renewal; ?>" />
<input type="hidden" name="p3" value="1" />
<input type="hidden" name="t3" value="<?php echo $billing_cycle; ?>" />
<input type="hidden" name="no-shipping" value="1" />
<?php
}
if ( ! empty( $settings['redirect_after_success']['url'] ) ) { ?>
<input type="hidden" name="return" value="<?php echo $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( 'button' );
foreach ( $this->get_errors() as $type => $message ) {
?>
<div class="elementor-message elementor-message-danger elementor-hidden elementor-error-message-<?php echo $type; ?>">
<?php echo $message; ?>
</div>
<?php
}
?>
</form>
<?php
}
// Render the payment button.
protected function render_button( $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' => __( 'Pricing & Payments', 'elementor-pro' ),
]
);
$this->add_control(
'merchant_account',
[
'label' => __( 'Merchant Account', 'elementor-pro' ),
'type' => Controls_Manager::HIDDEN,
'default' => self::API_TYPE_SIMPLE,
'options' => [
self::API_TYPE_SIMPLE => __( 'Default (Simple)', 'elementor-pro' ),
self::API_TYPE_ADVANCED => __( 'Custom (Advanced)', 'elementor-pro' ),
],
'frontend_available' => true,
]
);
$this->add_control(
'email',
[
'label' => __( 'PayPal Account', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'description' => __( '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' => __( 'SDK Token', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'label_block' => true,
'condition' => [
'merchant_account' => self::API_TYPE_ADVANCED,
],
]
);
$this->register_product_controls();
$this->end_controls_section();
}
// Custom sandbox controls.
protected function register_sandbox_controls() {
$this->add_control(
'sandbox_email',
[
'label' => __( 'Sandbox Email Account', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
],
'description' => __( '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',
],
]
);
}
}