first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,520 @@
<?php
/**
* Class WC_Gateway_BACS file.
*
* @package WooCommerce\Gateways
*/
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Internal\Admin\Settings\Utils as SettingsUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Bank Transfer Payment Gateway.
*
* Provides a Bank Transfer Payment Gateway. Based on code by Mike Pepper.
*
* @class WC_Gateway_BACS
* @extends WC_Payment_Gateway
* @version 2.1.0
* @package WooCommerce\Classes\Payment
*/
class WC_Gateway_BACS extends WC_Payment_Gateway {
/**
* Unique ID for this gateway.
*
* @var string
*/
const ID = 'bacs';
/**
* Array of locales
*
* @var array
*/
public $locale;
/**
* Gateway instructions that will be added to the thank you page and emails.
*
* @var string
*/
public $instructions;
/**
* Account details.
*
* @var array
*/
public $account_details;
/**
* Constructor for the gateway.
*/
public function __construct() {
$this->id = self::ID;
$this->icon = apply_filters( 'woocommerce_bacs_icon', '' );
$this->has_fields = false;
$this->method_title = __( 'Direct bank transfer', 'woocommerce' );
$this->method_description = __( 'Take payments in person via BACS. More commonly known as direct bank/wire transfer.', 'woocommerce' );
// Load the settings.
$this->init_form_fields();
$this->init_settings();
// Define user set variables.
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
$this->instructions = $this->get_option( 'instructions' );
// BACS account fields shown on the thanks page and in emails.
$this->account_details = get_option(
'woocommerce_bacs_accounts',
array(
array(
'account_name' => $this->get_option( 'account_name' ),
'account_number' => $this->get_option( 'account_number' ),
'sort_code' => $this->get_option( 'sort_code' ),
'bank_name' => $this->get_option( 'bank_name' ),
'iban' => $this->get_option( 'iban' ),
'bic' => $this->get_option( 'bic' ),
),
)
);
// Actions.
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'save_account_details' ) );
add_action( 'woocommerce_thankyou_bacs', array( $this, 'thankyou_page' ) );
// Customer Emails.
add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 );
}
/**
* Initialise Gateway Settings Form Fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable bank transfer', 'woocommerce' ),
'default' => 'no',
),
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'safe_text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => __( 'Direct bank transfer', 'woocommerce' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Payment method description that the customer will see on your checkout.', 'woocommerce' ),
'default' => __( 'Make your payment directly into our bank account. Please use your Order ID as the payment reference. Your order will not be shipped until the funds have cleared in our account.', 'woocommerce' ),
'desc_tip' => true,
),
'instructions' => array(
'title' => __( 'Instructions', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Instructions that will be added to the thank you page and emails.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
),
'account_details' => array(
'type' => 'account_details',
),
);
}
/**
* Generate account details html.
*
* @return string
*/
public function generate_account_details_html() {
ob_start();
$country = WC()->countries->get_base_country();
$locale = $this->get_country_locale();
// Get sortcode label in the $locale array and use appropriate one.
$sortcode = isset( $locale[ $country ]['sortcode']['label'] ) ? $locale[ $country ]['sortcode']['label'] : __( 'Sort code', 'woocommerce' );
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label>
<?php esc_html_e( 'Account details:', 'woocommerce' ); ?>
<?php echo wp_kses_post( wc_help_tip( __( 'These account details will be displayed within the order thank you page and confirmation email.', 'woocommerce' ) ) ); ?>
</label>
</th>
<td class="forminp" id="bacs_accounts">
<div class="wc_input_table_wrapper">
<table class="widefat wc_input_table sortable" cellspacing="0">
<thead>
<tr>
<th class="sort">&nbsp;</th>
<th><?php esc_html_e( 'Account name', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Account number', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Bank name', 'woocommerce' ); ?></th>
<th><?php echo esc_html( $sortcode ); ?></th>
<th><?php esc_html_e( 'IBAN', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'BIC / Swift', 'woocommerce' ); ?></th>
</tr>
</thead>
<tbody class="accounts">
<?php
$i = -1;
if ( $this->account_details ) {
foreach ( $this->account_details as $account ) {
++$i;
echo '<tr class="account">
<td class="sort"></td>
<td><input type="text" value="' . esc_attr( wp_unslash( $account['account_name'] ) ) . '" name="bacs_account_name[' . esc_attr( $i ) . ']" /></td>
<td><input type="text" value="' . esc_attr( $account['account_number'] ) . '" name="bacs_account_number[' . esc_attr( $i ) . ']" /></td>
<td><input type="text" value="' . esc_attr( wp_unslash( $account['bank_name'] ) ) . '" name="bacs_bank_name[' . esc_attr( $i ) . ']" /></td>
<td><input type="text" value="' . esc_attr( $account['sort_code'] ) . '" name="bacs_sort_code[' . esc_attr( $i ) . ']" /></td>
<td><input type="text" value="' . esc_attr( $account['iban'] ) . '" name="bacs_iban[' . esc_attr( $i ) . ']" /></td>
<td><input type="text" value="' . esc_attr( $account['bic'] ) . '" name="bacs_bic[' . esc_attr( $i ) . ']" /></td>
</tr>';
}
}
?>
</tbody>
<tfoot>
<tr>
<th colspan="7"><a href="#" class="add button"><?php esc_html_e( '+ Add account', 'woocommerce' ); ?></a> <a href="#" class="remove_rows button"><?php esc_html_e( 'Remove selected account(s)', 'woocommerce' ); ?></a></th>
</tr>
</tfoot>
</table>
</div>
<script type="text/javascript">
jQuery(function() {
jQuery('#bacs_accounts').on( 'click', 'a.add', function(){
var size = jQuery('#bacs_accounts').find('tbody .account').length;
jQuery('<tr class="account">\
<td class="sort"></td>\
<td><input type="text" name="bacs_account_name[' + size + ']" /></td>\
<td><input type="text" name="bacs_account_number[' + size + ']" /></td>\
<td><input type="text" name="bacs_bank_name[' + size + ']" /></td>\
<td><input type="text" name="bacs_sort_code[' + size + ']" /></td>\
<td><input type="text" name="bacs_iban[' + size + ']" /></td>\
<td><input type="text" name="bacs_bic[' + size + ']" /></td>\
</tr>').appendTo('#bacs_accounts table tbody');
return false;
});
});
</script>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Save account details table.
*/
public function save_account_details() {
$accounts = array();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification already handled in WC_Admin_Settings::save()
if ( isset( $_POST['bacs_account_name'] ) && isset( $_POST['bacs_account_number'] ) && isset( $_POST['bacs_bank_name'] )
&& isset( $_POST['bacs_sort_code'] ) && isset( $_POST['bacs_iban'] ) && isset( $_POST['bacs_bic'] ) ) {
$account_names = wc_clean( wp_unslash( $_POST['bacs_account_name'] ) );
$account_numbers = wc_clean( wp_unslash( $_POST['bacs_account_number'] ) );
$bank_names = wc_clean( wp_unslash( $_POST['bacs_bank_name'] ) );
$sort_codes = wc_clean( wp_unslash( $_POST['bacs_sort_code'] ) );
$ibans = wc_clean( wp_unslash( $_POST['bacs_iban'] ) );
$bics = wc_clean( wp_unslash( $_POST['bacs_bic'] ) );
foreach ( $account_names as $i => $name ) {
if ( ! isset( $account_names[ $i ] ) ) {
continue;
}
$accounts[] = array(
'account_name' => $account_names[ $i ],
'account_number' => $account_numbers[ $i ],
'bank_name' => $bank_names[ $i ],
'sort_code' => $sort_codes[ $i ],
'iban' => $ibans[ $i ],
'bic' => $bics[ $i ],
);
}
}
// phpcs:enable
do_action( 'woocommerce_update_option', array( 'id' => 'woocommerce_bacs_accounts' ) );
update_option( 'woocommerce_bacs_accounts', $accounts );
}
/**
* Output for the order received page.
*
* @param int $order_id Order ID.
*/
public function thankyou_page( $order_id ) {
if ( $this->instructions ) {
echo wp_kses_post( wpautop( wptexturize( wp_kses_post( $this->instructions ) ) ) );
}
$this->bank_details( $order_id );
}
/**
* Add content to the WC emails.
*
* @param WC_Order $order Order object.
* @param bool $sent_to_admin Sent to admin.
* @param bool $plain_text Email format: plain text or HTML.
*/
public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
if ( ! $sent_to_admin && self::ID === $order->get_payment_method() ) {
/**
* Filter the email instructions order status.
*
* @since 7.4
*
* @param string $terms The order status.
* @param object $order The order object.
*/
$instructions_order_status = apply_filters( 'woocommerce_bacs_email_instructions_order_status', OrderStatus::ON_HOLD, $order );
if ( $order->has_status( $instructions_order_status ) ) {
if ( $this->instructions ) {
echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) . PHP_EOL );
}
$this->bank_details( $order->get_id() );
}
}
}
/**
* Get bank details and place into a list format.
*
* @param int $order_id Order ID.
*/
private function bank_details( $order_id = '' ) {
if ( empty( $this->account_details ) ) {
return;
}
// Get order and store in $order.
$order = wc_get_order( $order_id );
// Get the order country and country $locale.
$country = $order->get_billing_country();
$locale = $this->get_country_locale();
// Get sortcode label in the $locale array and use appropriate one.
$sortcode = isset( $locale[ $country ]['sortcode']['label'] ) ? $locale[ $country ]['sortcode']['label'] : __( 'Sort code', 'woocommerce' );
$bacs_accounts = apply_filters( 'woocommerce_bacs_accounts', $this->account_details, $order_id );
if ( ! empty( $bacs_accounts ) ) {
$account_html = '';
$has_details = false;
foreach ( $bacs_accounts as $bacs_account ) {
$bacs_account = (object) $bacs_account;
if ( $bacs_account->account_name ) {
$account_html .= '<h3 class="wc-bacs-bank-details-account-name">' . wp_kses_post( wp_unslash( $bacs_account->account_name ) ) . ':</h3>' . PHP_EOL;
}
$account_html .= '<ul class="wc-bacs-bank-details order_details bacs_details">' . PHP_EOL;
// BACS account fields shown on the thanks page and in emails.
$account_fields = apply_filters(
'woocommerce_bacs_account_fields',
array(
'bank_name' => array(
'label' => __( 'Bank', 'woocommerce' ),
'value' => $bacs_account->bank_name,
),
'account_number' => array(
'label' => __( 'Account number', 'woocommerce' ),
'value' => $bacs_account->account_number,
),
'sort_code' => array(
'label' => $sortcode,
'value' => $bacs_account->sort_code,
),
'iban' => array(
'label' => __( 'IBAN', 'woocommerce' ),
'value' => $bacs_account->iban,
),
'bic' => array(
'label' => __( 'BIC', 'woocommerce' ),
'value' => $bacs_account->bic,
),
),
$order_id
);
foreach ( $account_fields as $field_key => $field ) {
if ( ! empty( $field['value'] ) ) {
$account_html .= '<li class="' . esc_attr( $field_key ) . '">' . wp_kses_post( $field['label'] ) . ': <strong>' . wp_kses_post( wptexturize( $field['value'] ) ) . '</strong></li>' . PHP_EOL;
$has_details = true;
}
}
$account_html .= '</ul>';
}
if ( $has_details ) {
echo '<section class="woocommerce-bacs-bank-details"><h2 class="wc-bacs-bank-details-heading">' . esc_html__( 'Our bank details', 'woocommerce' ) . '</h2>' . wp_kses_post( PHP_EOL . $account_html ) . '</section>';
}
}
}
/**
* Process the payment and return the result.
*
* @param int $order_id Order ID.
* @return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
if ( $order->get_total() > 0 ) {
/**
* Filter the order status for BACS payment.
*
* @since 3.4.0
*
* @param string $default_status The default order status.
* @param object $order The order object.
*/
$process_payment_status = apply_filters( 'woocommerce_bacs_process_payment_order_status', OrderStatus::ON_HOLD, $order );
// Mark as on-hold (we're awaiting the payment).
$order->update_status( $process_payment_status, __( 'Awaiting BACS payment.', 'woocommerce' ) );
} else {
$order->payment_complete();
}
// Remove cart.
WC()->cart->empty_cart();
// Return thankyou redirect.
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order ),
);
}
/**
* Get country locale if localized.
*
* @return array
*/
public function get_country_locale() {
if ( empty( $this->locale ) ) {
// Locale information to be used - only those that are not 'Sort Code'.
$this->locale = apply_filters(
'woocommerce_get_bacs_locale',
array(
'AU' => array(
'sortcode' => array(
'label' => __( 'BSB', 'woocommerce' ),
),
),
'CA' => array(
'sortcode' => array(
'label' => __( 'Bank transit number', 'woocommerce' ),
),
),
'IN' => array(
'sortcode' => array(
'label' => __( 'IFSC', 'woocommerce' ),
),
),
'IT' => array(
'sortcode' => array(
'label' => __( 'Branch sort', 'woocommerce' ),
),
),
'NZ' => array(
'sortcode' => array(
'label' => __( 'Bank code', 'woocommerce' ),
),
),
'SE' => array(
'sortcode' => array(
'label' => __( 'Bank code', 'woocommerce' ),
),
),
'US' => array(
'sortcode' => array(
'label' => __( 'Routing number', 'woocommerce' ),
),
),
'ZA' => array(
'sortcode' => array(
'label' => __( 'Branch code', 'woocommerce' ),
),
),
)
);
}
return $this->locale;
}
/**
* Get the settings URL for the gateway.
*
* @return string The settings page URL for the gateway.
*/
public function get_settings_url() {
$should_use_react_settings_page = $this->is_reactified_settings_page();
// We must not include both the path and the section query parameter, as this can cause weird behavior.
return SettingsUtils::wc_payments_settings_url(
$should_use_react_settings_page ? '/' . WC_Settings_Payment_Gateways::OFFLINE_SECTION_NAME . '/' . $this->id : null,
$should_use_react_settings_page ? array() : array( 'section' => $this->id )
);
}
/**
* Check if the BACS settings page is reactified.
*
* @return bool Whether the BACS settings page is reactified or not.
*/
private function is_reactified_settings_page(): bool {
// Search for a WC_Settings_Payment_Gateways instance in the settings pages.
$payments_settings_page = null;
foreach ( WC_Admin_Settings::get_settings_pages() as $settings_page ) {
if ( $settings_page instanceof WC_Settings_Payment_Gateways ) {
$payments_settings_page = $settings_page;
break;
}
}
// If no instance found, default to reactified.
if ( empty( $payments_settings_page ) ) {
return true;
}
return $payments_settings_page->should_render_react_section( WC_Settings_Payment_Gateways::BACS_SECTION_NAME );
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* Class WC_Gateway_Cheque file.
*
* @package WooCommerce\Gateways
*/
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Internal\Admin\Settings\Utils as SettingsUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Cheque Payment Gateway.
*
* Provides a Cheque Payment Gateway, mainly for testing purposes.
*
* @class WC_Gateway_Cheque
* @extends WC_Payment_Gateway
* @version 2.1.0
* @package WooCommerce\Classes\Payment
*/
class WC_Gateway_Cheque extends WC_Payment_Gateway {
/**
* Unique ID for this gateway.
*
* @var string
*/
const ID = 'cheque';
/**
* Gateway instructions that will be added to the thank you page and emails.
*
* @var string
*/
public $instructions;
/**
* Constructor for the gateway.
*/
public function __construct() {
$this->id = self::ID;
$this->icon = apply_filters( 'woocommerce_cheque_icon', '' );
$this->has_fields = false;
$this->method_title = _x( 'Check payments', 'Check payment method', 'woocommerce' );
$this->method_description = __( 'Take payments in person via checks. This offline gateway can also be useful to test purchases.', 'woocommerce' );
// Load the settings.
$this->init_form_fields();
$this->init_settings();
// Define user set variables.
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
$this->instructions = $this->get_option( 'instructions' );
// Actions.
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'woocommerce_thankyou_cheque', array( $this, 'thankyou_page' ) );
// Customer Emails.
add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 );
}
/**
* Initialise Gateway Settings Form Fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable check payments', 'woocommerce' ),
'default' => 'no',
),
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'safe_text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => _x( 'Check payments', 'Check payment method', 'woocommerce' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Payment method description that the customer will see on your checkout.', 'woocommerce' ),
'default' => __( 'Please send a check to Store Name, Store Street, Store Town, Store State / County, Store Postcode.', 'woocommerce' ),
'desc_tip' => true,
),
'instructions' => array(
'title' => __( 'Instructions', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Instructions that will be added to the thank you page and emails.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
),
);
}
/**
* Output for the order received page.
*/
public function thankyou_page() {
if ( $this->instructions ) {
echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) );
}
}
/**
* Add content to the WC emails.
*
* @access public
* @param WC_Order $order Order object.
* @param bool $sent_to_admin Sent to admin.
* @param bool $plain_text Email format: plain text or HTML.
*/
public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
if ( $this->instructions && ! $sent_to_admin && self::ID === $order->get_payment_method() ) {
/**
* Filter the email instructions order status.
*
* @since 7.4
*
* @param string $status The default status.
* @param object $order The order object.
*/
$instructions_order_status = apply_filters( 'woocommerce_cheque_email_instructions_order_status', OrderStatus::ON_HOLD, $order );
if ( $order->has_status( $instructions_order_status ) ) {
echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) . PHP_EOL );
}
}
}
/**
* Process the payment and return the result.
*
* @param int $order_id Order ID.
* @return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
if ( $order->get_total() > 0 ) {
/**
* Filter the order status for cheque payment.
*
* @since 3.6.0
*
* @param string $status The default status.
* @param object $order The order object.
*/
$process_payment_status = apply_filters( 'woocommerce_cheque_process_payment_order_status', OrderStatus::ON_HOLD, $order );
// Mark as on-hold (we're awaiting the cheque).
$order->update_status( $process_payment_status, _x( 'Awaiting check payment.', 'Check payment method', 'woocommerce' ) );
} else {
$order->payment_complete();
}
// Remove cart.
WC()->cart->empty_cart();
// Return thankyou redirect.
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order ),
);
}
/**
* Get the settings URL for the gateway.
*
* @return string The settings page URL for the gateway.
*/
public function get_settings_url() {
// Search for a WC_Settings_Payment_Gateways instance in the settings pages.
$payments_settings_page = null;
foreach ( WC_Admin_Settings::get_settings_pages() as $settings_page ) {
if ( $settings_page instanceof WC_Settings_Payment_Gateways ) {
$payments_settings_page = $settings_page;
break;
}
}
// If no instance found, return the default settings URL (the Reactified page).
if ( empty( $payments_settings_page ) ) {
return SettingsUtils::wc_payments_settings_url( '/' . WC_Settings_Payment_Gateways::OFFLINE_SECTION_NAME . '/' . $this->id );
}
$should_use_react_settings_page = $payments_settings_page->should_render_react_section( WC_Settings_Payment_Gateways::CHEQUE_SECTION_NAME );
// We must not include both the path and the section query parameter, as this can cause weird behavior.
return SettingsUtils::wc_payments_settings_url(
$should_use_react_settings_page ? '/' . WC_Settings_Payment_Gateways::OFFLINE_SECTION_NAME . '/' . $this->id : null,
$should_use_react_settings_page ? array() : array( 'section' => $this->id )
);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Class WC_Payment_Gateway_CC file.
*
* @package WooCommerce\Gateways
*/
use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Credit Card Payment Gateway
*
* @since 2.6.0
* @package WooCommerce\Classes
*/
class WC_Payment_Gateway_CC extends WC_Payment_Gateway {
/**
* Builds our payment fields area - including tokenization fields for logged
* in users, and the actual payment fields.
*
* @since 2.6.0
*/
public function payment_fields() {
if ( $this->supports( PaymentGatewayFeature::TOKENIZATION ) && is_checkout() ) {
$this->tokenization_script();
$this->saved_payment_methods();
$this->form();
$this->save_payment_method_checkbox();
} else {
$this->form();
}
}
/**
* Output field name HTML
*
* Gateways which support tokenization do not require names - we don't want the data to post to the server.
*
* @since 2.6.0
* @param string $name Field name.
* @return string
*/
public function field_name( $name ) {
return $this->supports( PaymentGatewayFeature::TOKENIZATION ) ? '' : ' name="' . esc_attr( $this->id . '-' . $name ) . '" ';
}
/**
* Outputs fields for entering credit card information.
*
* @since 2.6.0
*/
public function form() {
wp_enqueue_script( 'wc-credit-card-form' );
$fields = array();
$cvc_field = '<p class="form-row form-row-last">
<label for="' . esc_attr( $this->id ) . '-card-cvc">' . esc_html__( 'Card code', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-cvc" class="input-text wc-credit-card-form-card-cvc" inputmode="numeric" autocomplete="off" autocorrect="no" autocapitalize="no" spellcheck="no" type="tel" maxlength="4" placeholder="' . esc_attr__( 'CVC', 'woocommerce' ) . '" ' . $this->field_name( 'card-cvc' ) . ' style="width:100px" />
</p>';
$default_fields = array(
'card-number-field' => '<p class="form-row form-row-wide">
<label for="' . esc_attr( $this->id ) . '-card-number">' . esc_html__( 'Card number', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-number" class="input-text wc-credit-card-form-card-number" inputmode="numeric" autocomplete="cc-number" autocorrect="no" autocapitalize="no" spellcheck="no" type="tel" placeholder="&bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull;" ' . $this->field_name( 'card-number' ) . ' />
</p>',
'card-expiry-field' => '<p class="form-row form-row-first">
<label for="' . esc_attr( $this->id ) . '-card-expiry">' . esc_html__( 'Expiry (MM/YY)', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-expiry" class="input-text wc-credit-card-form-card-expiry" inputmode="numeric" autocomplete="cc-exp" autocorrect="no" autocapitalize="no" spellcheck="no" type="tel" placeholder="' . esc_attr__( 'MM / YY', 'woocommerce' ) . '" ' . $this->field_name( 'card-expiry' ) . ' />
</p>',
);
if ( ! $this->supports( PaymentGatewayFeature::CREDIT_CARD_FORM_CVC_ON_SAVED_METHOD ) ) {
$default_fields['card-cvc-field'] = $cvc_field;
}
$fields = wp_parse_args( $fields, apply_filters( 'woocommerce_credit_card_form_fields', $default_fields, $this->id ) );
?>
<fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-cc-form" class='wc-credit-card-form wc-payment-form'>
<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
<?php
foreach ( $fields as $field ) {
echo $field; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
}
?>
<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
<div class="clear"></div>
</fieldset>
<?php
if ( $this->supports( PaymentGatewayFeature::CREDIT_CARD_FORM_CVC_ON_SAVED_METHOD ) ) {
echo '<fieldset>' . $cvc_field . '</fieldset>'; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Class WC_Payment_Gateway_eCheck file.
*
* @package WooCommerce\Gateways
*/
use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for eCheck Payment Gateway
*
* @since 2.6.0
* @package WooCommerce\Classes
*/
class WC_Payment_Gateway_ECheck extends WC_Payment_Gateway {
/**
* Builds our payment fields area - including tokenization fields for logged
* in users, and the actual payment fields.
*
* @since 2.6.0
*/
public function payment_fields() {
if ( $this->supports( PaymentGatewayFeature::TOKENIZATION ) && is_checkout() ) {
$this->tokenization_script();
$this->saved_payment_methods();
$this->form();
$this->save_payment_method_checkbox();
} else {
$this->form();
}
}
/**
* Outputs fields for entering eCheck information.
*
* @since 2.6.0
*/
public function form() {
$fields = array();
$default_fields = array(
'routing-number' => '<p class="form-row form-row-first">
<label for="' . esc_attr( $this->id ) . '-routing-number">' . esc_html__( 'Routing number', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-routing-number" class="input-text wc-echeck-form-routing-number" type="text" maxlength="9" autocomplete="off" placeholder="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;" name="' . esc_attr( $this->id ) . '-routing-number" />
</p>',
'account-number' => '<p class="form-row form-row-wide">
<label for="' . esc_attr( $this->id ) . '-account-number">' . esc_html__( 'Account number', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-account-number" class="input-text wc-echeck-form-account-number" type="text" autocomplete="off" name="' . esc_attr( $this->id ) . '-account-number" maxlength="17" />
</p>',
);
$fields = wp_parse_args( $fields, apply_filters( 'woocommerce_echeck_form_fields', $default_fields, $this->id ) );
?>
<fieldset id="<?php echo esc_attr( $this->id ); ?>-cc-form" class='wc-echeck-form wc-payment-form'>
<?php do_action( 'woocommerce_echeck_form_start', $this->id ); ?>
<?php
foreach ( $fields as $field ) {
echo $field; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
}
?>
<?php do_action( 'woocommerce_echeck_form_end', $this->id ); ?>
<div class="clear"></div>
</fieldset>
<?php
}
}

View File

@@ -0,0 +1,399 @@
<?php
/**
* Class WC_Gateway_COD file.
*
* @package WooCommerce\Gateways
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Internal\Admin\Settings\Utils as SettingsUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Cash on Delivery Gateway.
*
* Provides a Cash on Delivery Payment Gateway.
*
* @class WC_Gateway_COD
* @extends WC_Payment_Gateway
* @version 2.1.0
* @package WooCommerce\Classes\Payment
*/
class WC_Gateway_COD extends WC_Payment_Gateway {
/**
* Unique ID for this gateway.
*
* @var string
*/
const ID = 'cod';
/**
* Gateway instructions that will be added to the thank you page and emails.
*
* @var string
*/
public $instructions;
/**
* Enable for shipping methods.
*
* @var array
*/
public $enable_for_methods;
/**
* Enable for virtual products.
*
* @var bool
*/
public $enable_for_virtual;
/**
* Constructor for the gateway.
*/
public function __construct() {
// Setup general properties.
$this->setup_properties();
// Load the settings.
$this->init_form_fields();
$this->init_settings();
// Get settings.
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
$this->instructions = $this->get_option( 'instructions' );
$this->enable_for_methods = $this->get_option( 'enable_for_methods', array() );
$this->enable_for_virtual = $this->get_option( 'enable_for_virtual', 'yes' ) === 'yes';
// Actions.
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) );
add_filter( 'woocommerce_payment_complete_order_status', array( $this, 'change_payment_complete_order_status' ), 10, 3 );
// Customer Emails.
add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 );
}
/**
* Setup general properties for the gateway.
*/
protected function setup_properties() {
$this->id = self::ID;
$this->icon = apply_filters( 'woocommerce_cod_icon', '' );
$this->method_title = __( 'Cash on delivery', 'woocommerce' );
$this->method_description = __( 'Let your shoppers pay upon delivery — by cash or other methods of payment.', 'woocommerce' );
$this->has_fields = false;
}
/**
* Initialise Gateway Settings Form Fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'label' => __( 'Enable cash on delivery', 'woocommerce' ),
'type' => 'checkbox',
'description' => '',
'default' => 'no',
),
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'safe_text',
'description' => __( 'Payment method description that the customer will see on your checkout.', 'woocommerce' ),
'default' => __( 'Cash on delivery', 'woocommerce' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Payment method description that the customer will see on your website.', 'woocommerce' ),
'default' => __( 'Pay with cash upon delivery.', 'woocommerce' ),
'desc_tip' => true,
),
'instructions' => array(
'title' => __( 'Instructions', 'woocommerce' ),
'type' => 'textarea',
'description' => __( 'Instructions that will be added to the thank you page.', 'woocommerce' ),
'default' => __( 'Pay with cash upon delivery.', 'woocommerce' ),
'desc_tip' => true,
),
'enable_for_methods' => array(
'title' => __( 'Enable for shipping methods', 'woocommerce' ),
'type' => 'multiselect',
'class' => 'wc-enhanced-select',
'css' => 'width: 400px;',
'default' => '',
'description' => __( 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', 'woocommerce' ),
'options' => $this->load_shipping_method_options(),
'desc_tip' => true,
'custom_attributes' => array(
'data-placeholder' => __( 'Select shipping methods', 'woocommerce' ),
),
),
'enable_for_virtual' => array(
'title' => __( 'Accept for virtual orders', 'woocommerce' ),
'label' => __( 'Accept COD if the order is virtual', 'woocommerce' ),
'type' => 'checkbox',
'default' => 'yes',
),
);
}
/**
* Check If The Gateway Is Available For Use.
*
* @return bool
*/
public function is_available() {
$is_virtual = true;
$shipping_methods = array();
// Get shipping methods from the cart or order.
if ( is_wc_endpoint_url( 'order-pay' ) ) {
$order = wc_get_order( absint( get_query_var( 'order-pay' ) ) );
$shipping_methods = $order ? $order->get_shipping_methods() : array();
$is_virtual = ! count( $shipping_methods );
} elseif ( WC()->cart && WC()->cart->needs_shipping() ) {
$shipping_methods = WC()->cart->get_shipping_methods();
$is_virtual = false;
}
// If COD is not enabled for virtual orders and the order does not need shipping, return false.
if ( ! $this->enable_for_virtual && $is_virtual ) {
return false;
}
// Return early if:
// - There are no shipping methods resrictions in place.
// - The order is virtual so needs no shipping.
// - Shipping methods are not set yet.
if ( empty( $this->enable_for_methods ) || $is_virtual || ! $shipping_methods ) {
return parent::is_available();
}
// Get the selected shipping method ids. This works on both WC_Shipping_Rate and WC_Order_Item_Shipping class instances.
$canonical_rate_ids = array_unique(
array_values(
array_map(
function ( $shipping_method ) {
return $shipping_method && is_callable( array( $shipping_method, 'get_method_id' ) ) && is_callable( array( $shipping_method, 'get_instance_id' ) ) ? $shipping_method->get_method_id() . ':' . $shipping_method->get_instance_id() : null;
},
$shipping_methods
)
)
);
if ( ! count( $this->get_matching_rates( $canonical_rate_ids ) ) ) {
return false;
}
return parent::is_available();
}
/**
* Checks to see whether or not the admin settings are being accessed by the current request.
*
* @return bool
*/
private function is_accessing_settings() {
if ( is_admin() ) {
if ( ! is_wc_admin_settings_page() ) {
return false;
}
// phpcs:disable WordPress.Security.NonceVerification
if ( ! isset( $_REQUEST['tab'] ) || 'checkout' !== $_REQUEST['tab'] ) {
return false;
}
if ( ! isset( $_REQUEST['section'] ) || self::ID !== $_REQUEST['section'] ) {
return false;
}
// phpcs:enable WordPress.Security.NonceVerification
return true;
}
if ( Constants::is_true( 'REST_REQUEST' ) ) {
global $wp;
if ( isset( $wp->query_vars['rest_route'] ) && false !== strpos( $wp->query_vars['rest_route'], '/payment_gateways' ) ) {
return true;
}
}
return false;
}
/**
* Loads all of the shipping method options for the enable_for_methods field.
*
* @return array
*/
private function load_shipping_method_options() {
// Since this is expensive, we only want to do it if we're actually on the settings page.
if ( ! $this->is_accessing_settings() ) {
return array();
}
$data_store = WC_Data_Store::load( 'shipping-zone' );
$raw_zones = $data_store->get_zones();
$zones = array();
foreach ( $raw_zones as $raw_zone ) {
$zones[] = new WC_Shipping_Zone( $raw_zone );
}
$zones[] = new WC_Shipping_Zone( 0 );
$options = array();
foreach ( WC()->shipping()->load_shipping_methods() as $method ) {
$options[ $method->get_method_title() ] = array();
// Translators: %1$s shipping method name.
$options[ $method->get_method_title() ][ $method->id ] = sprintf( __( 'Any &quot;%1$s&quot; method', 'woocommerce' ), $method->get_method_title() );
foreach ( $zones as $zone ) {
$shipping_method_instances = $zone->get_shipping_methods();
foreach ( $shipping_method_instances as $shipping_method_instance_id => $shipping_method_instance ) {
if ( $shipping_method_instance->id !== $method->id ) {
continue;
}
$option_id = $shipping_method_instance->get_rate_id();
// Translators: %1$s shipping method title, %2$s shipping method id.
$option_instance_title = sprintf( __( '%1$s (#%2$s)', 'woocommerce' ), $shipping_method_instance->get_title(), $shipping_method_instance_id );
// Translators: %1$s zone name, %2$s shipping method instance name.
$option_title = sprintf( __( '%1$s &ndash; %2$s', 'woocommerce' ), $zone->get_id() ? $zone->get_zone_name() : __( 'Other locations', 'woocommerce' ), $option_instance_title );
$options[ $method->get_method_title() ][ $option_id ] = $option_title;
}
}
}
return $options;
}
/**
* Indicates whether a rate exists in an array of canonically-formatted rate IDs that activates this gateway.
*
* @since 3.4.0
*
* @param array $rate_ids Rate ids to check.
* @return array
*/
private function get_matching_rates( $rate_ids ) {
// First, match entries in 'method_id:instance_id' format. Then, match entries in 'method_id' format by stripping off the instance ID from the candidates.
return array_unique( array_merge( array_intersect( $this->enable_for_methods, $rate_ids ), array_intersect( $this->enable_for_methods, array_unique( array_map( 'wc_get_string_before_colon', $rate_ids ) ) ) ) );
}
/**
* Process the payment and return the result.
*
* @param int $order_id Order ID.
* @return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
if ( $order->get_total() > 0 ) {
/**
* Filter the order status for COD orders.
*
* @since 2.6.0
*
* @param string $order_status Default status for COD orders.
*/
$process_payment_status = apply_filters( 'woocommerce_cod_process_payment_order_status', $order->has_downloadable_item() ? OrderStatus::ON_HOLD : OrderStatus::PROCESSING, $order );
// Mark as processing or on-hold (payment won't be taken until delivery).
$order->update_status( $process_payment_status, __( 'Payment to be made upon delivery.', 'woocommerce' ) );
} else {
$order->payment_complete();
}
// Remove cart.
WC()->cart->empty_cart();
// Return thankyou redirect.
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order ),
);
}
/**
* Output for the order received page.
*/
public function thankyou_page() {
if ( $this->instructions ) {
echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) );
}
}
/**
* Change payment complete order status to completed for COD orders.
*
* @since 3.1.0
* @param string $status Current order status.
* @param int $order_id Order ID.
* @param WC_Order|false $order Order object.
* @return string
*/
public function change_payment_complete_order_status( $status, $order_id = 0, $order = false ) {
if ( $order && self::ID === $order->get_payment_method() ) {
$status = OrderStatus::COMPLETED;
}
return $status;
}
/**
* Add content to the WC emails.
*
* @param WC_Order $order Order object.
* @param bool $sent_to_admin Sent to admin.
* @param bool $plain_text Email format: plain text or HTML.
*/
public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
if ( $this->instructions && ! $sent_to_admin && $this->id === $order->get_payment_method() ) {
echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) . PHP_EOL );
}
}
/**
* Get the settings URL for the gateway.
*
* @return string The settings page URL for the gateway.
*/
public function get_settings_url() {
// Search for a WC_Settings_Payment_Gateways instance in the settings pages.
$payments_settings_page = null;
foreach ( WC_Admin_Settings::get_settings_pages() as $settings_page ) {
if ( $settings_page instanceof WC_Settings_Payment_Gateways ) {
$payments_settings_page = $settings_page;
break;
}
}
// If no instance found, return the default settings URL (the Reactified page).
if ( empty( $payments_settings_page ) ) {
return SettingsUtils::wc_payments_settings_url( '/' . WC_Settings_Payment_Gateways::OFFLINE_SECTION_NAME . '/' . $this->id );
}
$should_use_react_settings_page = $payments_settings_page->should_render_react_section( WC_Settings_Payment_Gateways::COD_SECTION_NAME );
// We must not include both the path and the section query parameter, as this can cause weird behavior.
return SettingsUtils::wc_payments_settings_url(
$should_use_react_settings_page ? '/' . WC_Settings_Payment_Gateways::OFFLINE_SECTION_NAME . '/' . $this->id : null,
$should_use_react_settings_page ? array() : array( 'section' => $this->id )
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,46 @@
jQuery( function( $ ) {
'use strict';
/**
* Object to handle PayPal admin functions.
*/
var wc_paypal_admin = {
isTestMode: function() {
return $( '#woocommerce_paypal_testmode' ).is( ':checked' );
},
/**
* Initialize.
*/
init: function() {
$( document.body ).on( 'change', '#woocommerce_paypal_testmode', function() {
var test_api_username = $( '#woocommerce_paypal_sandbox_api_username' ).parents( 'tr' ).eq( 0 ),
test_api_password = $( '#woocommerce_paypal_sandbox_api_password' ).parents( 'tr' ).eq( 0 ),
test_api_signature = $( '#woocommerce_paypal_sandbox_api_signature' ).parents( 'tr' ).eq( 0 ),
live_api_username = $( '#woocommerce_paypal_api_username' ).parents( 'tr' ).eq( 0 ),
live_api_password = $( '#woocommerce_paypal_api_password' ).parents( 'tr' ).eq( 0 ),
live_api_signature = $( '#woocommerce_paypal_api_signature' ).parents( 'tr' ).eq( 0 );
if ( $( this ).is( ':checked' ) ) {
test_api_username.show();
test_api_password.show();
test_api_signature.show();
live_api_username.hide();
live_api_password.hide();
live_api_signature.hide();
} else {
test_api_username.hide();
test_api_password.hide();
test_api_signature.hide();
live_api_username.show();
live_api_password.show();
live_api_signature.show();
}
} );
$( '#woocommerce_paypal_testmode' ).trigger( 'change' );
}
};
wc_paypal_admin.init();
});

View File

@@ -0,0 +1 @@
jQuery(function(t){"use strict";(function(){t(document.body).on("change","#woocommerce_paypal_testmode",function(){var e=t("#woocommerce_paypal_sandbox_api_username").parents("tr").eq(0),o=t("#woocommerce_paypal_sandbox_api_password").parents("tr").eq(0),a=t("#woocommerce_paypal_sandbox_api_signature").parents("tr").eq(0),r=t("#woocommerce_paypal_api_username").parents("tr").eq(0),p=t("#woocommerce_paypal_api_password").parents("tr").eq(0),s=t("#woocommerce_paypal_api_signature").parents("tr").eq(0);t(this).is(":checked")?(e.show(),o.show(),a.show(),r.hide(),p.hide(),s.hide()):(e.hide(),o.hide(),a.hide(),r.show(),p.show(),s.show())}),t("#woocommerce_paypal_testmode").trigger("change")})()});

View File

@@ -0,0 +1,147 @@
<?php
/**
* Class WC_Gateway_Paypal_Buttons file.
*
* @package WooCommerce\Gateways
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons instead. This class will be removed in 11.0.0.
*/
declare(strict_types=1);
use Automattic\WooCommerce\Gateways\PayPal\Buttons as PayPalButtons;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Gateway_Paypal_Request' ) ) {
require_once __DIR__ . '/includes/class-wc-gateway-paypal-request.php';
}
/**
* Handles PayPal Buttons.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons instead. This class will be removed in 11.0.0.
*/
class WC_Gateway_Paypal_Buttons {
/**
* The delegated buttons instance.
*
* @var PayPalButtons
*/
private $buttons;
/**
* Constructor.
*
* @param WC_Gateway_Paypal $gateway The gateway instance.
*/
public function __construct( WC_Gateway_Paypal $gateway ) {
$this->buttons = new PayPalButtons( $gateway );
}
/**
* Get the options for the PayPal buttons.
*
* @return array
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_options() instead. This method will be removed in 11.0.0.
*/
public function get_options() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_options() instead.'
);
return $this->buttons->get_options();
}
/**
* Get the common attributes for the PayPal JS SDK script and modules.
*
* @return array
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_common_options() instead. This method will be removed in 11.0.0.
*/
public function get_common_options() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_common_options() instead.'
);
return $this->buttons->get_common_options();
}
/**
* Get the client-id for the PayPal buttons.
*
* @return string|null The PayPal client-id, or null if the request fails.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_client_id() instead. This method will be removed in 11.0.0.
*/
public function get_client_id() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_client_id() instead.'
);
return $this->buttons->get_client_id();
}
/**
* Get the page type for the PayPal buttons.
*
* @return string
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_page_type() instead. This method will be removed in 11.0.0.
*/
public function get_page_type() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_page_type() instead.'
);
return $this->buttons->get_page_type();
}
/**
* Whether PayPal Buttons is enabled.
*
* @return bool
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::is_enabled() instead. This method will be removed in 11.0.0.
*/
public function is_enabled() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::is_enabled() instead.'
);
return $this->buttons->is_enabled();
}
/**
* Get the current page URL, to be used for App Switch.
* Limited to checkout, cart, and product pages for security.
*
* @return string
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_current_page_for_app_switch() instead. This method will be removed in 11.0.0.
*/
public function get_current_page_for_app_switch() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
'Use Automattic\WooCommerce\Gateways\PayPal\Buttons::get_current_page_for_app_switch() instead.'
);
return $this->buttons->get_current_page_for_app_switch();
}
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* Class WC_Gateway_Paypal_API_Handler file.
*
* @package WooCommerce\Gateways
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles Refunds and other API requests such as capture.
*
* @since 3.0.0
*/
class WC_Gateway_Paypal_API_Handler {
/**
* API Username
*
* @var string
*/
public static $api_username;
/**
* API Password
*
* @var string
*/
public static $api_password;
/**
* API Signature
*
* @var string
*/
public static $api_signature;
/**
* Sandbox
*
* @var bool
*/
public static $sandbox = false;
/**
* Get capture request args.
* See https://developer.paypal.com/docs/classic/api/merchant/DoCapture_API_Operation_NVP/.
*
* @param WC_Order $order Order object.
* @param float $amount Amount.
* @return array
*/
public static function get_capture_request( $order, $amount = null ) {
$request = array(
'VERSION' => '84.0',
'SIGNATURE' => self::$api_signature,
'USER' => self::$api_username,
'PWD' => self::$api_password,
'METHOD' => 'DoCapture',
'AUTHORIZATIONID' => $order->get_transaction_id(),
'AMT' => number_format( is_null( $amount ) ? $order->get_total() : $amount, 2, '.', '' ),
'CURRENCYCODE' => $order->get_currency(),
'COMPLETETYPE' => 'Complete',
);
return apply_filters( 'woocommerce_paypal_capture_request', $request, $order, $amount );
}
/**
* Get refund request args.
*
* @param WC_Order $order Order object.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
* @return array
*/
public static function get_refund_request( $order, $amount = null, $reason = '' ) {
$request = array(
'VERSION' => '84.0',
'SIGNATURE' => self::$api_signature,
'USER' => self::$api_username,
'PWD' => self::$api_password,
'METHOD' => 'RefundTransaction',
'TRANSACTIONID' => $order->get_transaction_id(),
'NOTE' => html_entity_decode( wc_trim_string( $reason, 255 ), ENT_NOQUOTES, 'UTF-8' ),
'REFUNDTYPE' => 'Full',
);
if ( ! is_null( $amount ) ) {
$request['AMT'] = number_format( $amount, 2, '.', '' );
$request['CURRENCYCODE'] = $order->get_currency();
$request['REFUNDTYPE'] = 'Partial';
}
return apply_filters( 'woocommerce_paypal_refund_request', $request, $order, $amount, $reason );
}
/**
* Capture an authorization.
*
* @param WC_Order $order Order object.
* @param float $amount Amount.
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/
public static function do_capture( $order, $amount = null ) {
$raw_response = wp_safe_remote_post(
self::$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_capture_request( $order, $amount ),
'timeout' => 70,
'user-agent' => 'WooCommerce/' . WC()->version,
'httpversion' => '1.1',
)
);
WC_Gateway_Paypal::log( 'DoCapture Response: ' . wc_print_r( $raw_response, true ) );
if ( is_wp_error( $raw_response ) ) {
return $raw_response;
} elseif ( empty( $raw_response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );
}
parse_str( $raw_response['body'], $response );
return (object) $response;
}
/**
* Refund an order via PayPal.
*
* @param WC_Order $order Order object.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/
public static function refund_transaction( $order, $amount = null, $reason = '' ) {
$raw_response = wp_safe_remote_post(
self::$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_refund_request( $order, $amount, $reason ),
'timeout' => 70,
'user-agent' => 'WooCommerce/' . WC()->version,
'httpversion' => '1.1',
)
);
WC_Gateway_Paypal::log( 'Refund Response: ' . wc_print_r( $raw_response, true ) );
if ( is_wp_error( $raw_response ) ) {
return $raw_response;
} elseif ( empty( $raw_response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );
}
parse_str( $raw_response['body'], $response );
return (object) $response;
}
}
/**
* Here for backwards compatibility.
*
* @since 3.0.0
*/
class WC_Gateway_Paypal_Refund extends WC_Gateway_Paypal_API_Handler {
/**
* Get refund request args. Proxy to WC_Gateway_Paypal_API_Handler::get_refund_request().
*
* @param WC_Order $order Order object.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
*
* @return array
*/
public static function get_request( $order, $amount = null, $reason = '' ) {
return self::get_refund_request( $order, $amount, $reason );
}
/**
* Process an order refund.
*
* @param WC_Order $order Order object.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
* @param bool $sandbox Whether to use sandbox mode or not.
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/
public static function refund_order( $order, $amount = null, $reason = '', $sandbox = false ) {
if ( $sandbox ) {
self::$sandbox = $sandbox;
}
$result = self::refund_transaction( $order, $amount, $reason );
if ( is_wp_error( $result ) ) {
return $result;
} else {
return (array) $result;
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* PayPal Gateway Constants.
*
* Provides constants for PayPal payment statuses, intents, and other PayPal-related values.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants instead. This class will be removed in 11.0.0.
* @version 10.3.0
* @package WooCommerce\Gateways
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
/**
* WC_Gateway_Paypal_Constants Class.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants instead. This class will be removed in 11.0.0.
*/
class WC_Gateway_Paypal_Constants {
/**
* PayPal proxy request timeout.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::WPCOM_PROXY_REQUEST_TIMEOUT instead.
*/
const WPCOM_PROXY_REQUEST_TIMEOUT = PayPalConstants::WPCOM_PROXY_REQUEST_TIMEOUT;
/**
* PayPal payment statuses.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::STATUS_* instead.
*/
const STATUS_COMPLETED = PayPalConstants::STATUS_COMPLETED;
const STATUS_APPROVED = PayPalConstants::STATUS_APPROVED;
const STATUS_CAPTURED = PayPalConstants::STATUS_CAPTURED;
const STATUS_AUTHORIZED = PayPalConstants::STATUS_AUTHORIZED;
const STATUS_PAYER_ACTION_REQUIRED = PayPalConstants::STATUS_PAYER_ACTION_REQUIRED;
const VOIDED = PayPalConstants::VOIDED;
/**
* PayPal payment intents.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::INTENT_* instead.
*/
const INTENT_CAPTURE = PayPalConstants::INTENT_CAPTURE;
const INTENT_AUTHORIZE = PayPalConstants::INTENT_AUTHORIZE;
/**
* PayPal payment actions.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::PAYMENT_ACTION_* instead.
*/
const PAYMENT_ACTION_CAPTURE = PayPalConstants::PAYMENT_ACTION_CAPTURE;
const PAYMENT_ACTION_AUTHORIZE = PayPalConstants::PAYMENT_ACTION_AUTHORIZE;
/**
* PayPal shipping preferences.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::SHIPPING_* instead.
*/
const SHIPPING_NO_SHIPPING = PayPalConstants::SHIPPING_NO_SHIPPING;
const SHIPPING_GET_FROM_FILE = PayPalConstants::SHIPPING_GET_FROM_FILE;
const SHIPPING_SET_PROVIDED_ADDRESS = PayPalConstants::SHIPPING_SET_PROVIDED_ADDRESS;
/**
* PayPal user actions.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::USER_ACTION_* instead.
*/
const USER_ACTION_PAY_NOW = PayPalConstants::USER_ACTION_PAY_NOW;
/**
* Maximum lengths for PayPal fields.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::PAYPAL_* instead.
*/
const PAYPAL_ORDER_ITEM_NAME_MAX_LENGTH = PayPalConstants::PAYPAL_ORDER_ITEM_NAME_MAX_LENGTH;
const PAYPAL_INVOICE_ID_MAX_LENGTH = PayPalConstants::PAYPAL_INVOICE_ID_MAX_LENGTH;
const PAYPAL_ADDRESS_LINE_MAX_LENGTH = PayPalConstants::PAYPAL_ADDRESS_LINE_MAX_LENGTH;
const PAYPAL_COUNTRY_CODE_LENGTH = PayPalConstants::PAYPAL_COUNTRY_CODE_LENGTH;
const PAYPAL_STATE_MAX_LENGTH = PayPalConstants::PAYPAL_STATE_MAX_LENGTH;
const PAYPAL_CITY_MAX_LENGTH = PayPalConstants::PAYPAL_CITY_MAX_LENGTH;
const PAYPAL_POSTAL_CODE_MAX_LENGTH = PayPalConstants::PAYPAL_POSTAL_CODE_MAX_LENGTH;
const PAYPAL_LOCALE_MAX_LENGTH = PayPalConstants::PAYPAL_LOCALE_MAX_LENGTH;
/**
* Supported payment sources.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::PAYMENT_SOURCE_* instead.
*/
const PAYMENT_SOURCE_PAYPAL = PayPalConstants::PAYMENT_SOURCE_PAYPAL;
const PAYMENT_SOURCE_VENMO = PayPalConstants::PAYMENT_SOURCE_VENMO;
const PAYMENT_SOURCE_PAYLATER = PayPalConstants::PAYMENT_SOURCE_PAYLATER;
const SUPPORTED_PAYMENT_SOURCES = PayPalConstants::SUPPORTED_PAYMENT_SOURCES;
/**
* Fields to redact from logs.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::FIELDS_TO_REDACT instead.
* @var array
*/
const FIELDS_TO_REDACT = PayPalConstants::FIELDS_TO_REDACT;
/**
* List of currencies supported by PayPal (Orders API V2).
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Constants::SUPPORTED_CURRENCIES instead.
* @var array<string>
*/
const SUPPORTED_CURRENCIES = PayPalConstants::SUPPORTED_CURRENCIES;
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* PayPal Helper Class
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper instead. This class will be removed in 11.0.0.
* @package WooCommerce\Gateways
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Gateways\PayPal\Helper as PayPalHelper;
if ( ! class_exists( 'WC_Gateway_Paypal_Constants' ) ) {
require_once __DIR__ . '/class-wc-gateway-paypal-constants.php';
}
/**
* Helper for PayPal gateway.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper instead. This class will be removed in 11.0.0.
*/
class WC_Gateway_Paypal_Helper {
/**
* Check if the PayPal gateway is enabled.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::is_paypal_gateway_available() instead.
* @return bool
*/
public static function is_paypal_gateway_available() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::is_paypal_gateway_available()' );
return PayPalHelper::is_paypal_gateway_available();
}
/**
* Check if the merchant is eligible for migration from WPS to PPCP.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::is_orders_v2_migration_eligible() instead.
* @return bool
*/
public static function is_orders_v2_migration_eligible() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::is_orders_v2_migration_eligible()' );
return PayPalHelper::is_orders_v2_migration_eligible();
}
/**
* Get the WC order from the PayPal custom ID.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::get_wc_order_from_paypal_custom_id() instead.
* @param string $custom_id The custom ID string from the PayPal order.
* @return WC_Order|null
*/
public static function get_wc_order_from_paypal_custom_id( $custom_id ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::get_wc_order_from_paypal_custom_id()' );
if ( ! is_string( $custom_id ) || '' === $custom_id ) {
return null;
}
return PayPalHelper::get_wc_order_from_paypal_custom_id( (string) $custom_id );
}
/**
* Remove PII (Personally Identifiable Information) from data for logging.
*
* This function recursively traverses the data array and redacts sensitive information
* while preserving the structure for debugging purposes.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::redact_data() instead.
* @param mixed $data The data to remove PII from (array, string, or other types).
* @return mixed The data with PII redacted.
*/
public static function redact_data( $data ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::redact_data()' );
return PayPalHelper::redact_data( $data );
}
/**
* Mask email address before @ keeping the full domain.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::mask_email() instead.
* @param string $email The email address to mask.
* @return string The masked email address or original input if invalid.
*/
public static function mask_email( $email ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::mask_email()' );
return PayPalHelper::mask_email( (string) $email );
}
/**
* Update the addresses in the order.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::update_addresses_in_order() instead.
* @param WC_Order|null $order The order object.
* @param array $paypal_order_details The PayPal order details.
* @return void
*/
public static function update_addresses_in_order( ?WC_Order $order, array $paypal_order_details ): void {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Helper::update_addresses_in_order()' );
PayPalHelper::update_addresses_in_order( $order, $paypal_order_details );
}
}

View File

@@ -0,0 +1,380 @@
<?php
/**
* Handles responses from PayPal IPN.
*
* @package WooCommerce\PayPal
* @version 3.3.0
*/
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once dirname( __FILE__ ) . '/class-wc-gateway-paypal-response.php';
/**
* WC_Gateway_Paypal_IPN_Handler class.
*/
class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
/**
* Receiver email address to validate.
*
* @var string Receiver email address.
*/
protected $receiver_email;
/**
* Constructor.
*
* @param bool $sandbox Use sandbox or not.
* @param string $receiver_email Email to receive IPN from.
*/
public function __construct( $sandbox = false, $receiver_email = '' ) {
add_action( 'woocommerce_api_wc_gateway_paypal', array( $this, 'check_response' ) );
add_action( 'valid-paypal-standard-ipn-request', array( $this, 'valid_response' ) );
$this->receiver_email = $receiver_email;
$this->sandbox = $sandbox;
}
/**
* Check for PayPal IPN Response.
*/
public function check_response() {
if ( ! empty( $_POST ) && $this->validate_ipn() ) { // WPCS: CSRF ok.
$posted = wp_unslash( $_POST ); // WPCS: CSRF ok, input var ok.
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
do_action( 'valid-paypal-standard-ipn-request', $posted );
exit;
}
wp_die( 'PayPal IPN Request Failure', 'PayPal IPN', array( 'response' => 500 ) );
}
/**
* There was a valid response.
*
* @param array $posted Post data after wp_unslash.
*/
public function valid_response( $posted ) {
$order = ! empty( $posted['custom'] ) ? $this->get_paypal_order( $posted['custom'] ) : false;
if ( $order ) {
// Lowercase returned variables.
$posted['payment_status'] = strtolower( $posted['payment_status'] );
WC_Gateway_Paypal::log( 'Found order #' . $order->get_id() );
WC_Gateway_Paypal::log( 'Payment status: ' . $posted['payment_status'] );
if ( method_exists( $this, 'payment_status_' . $posted['payment_status'] ) ) {
call_user_func( array( $this, 'payment_status_' . $posted['payment_status'] ), $order, $posted );
}
}
}
/**
* Check PayPal IPN validity.
*/
public function validate_ipn() {
WC_Gateway_Paypal::log( 'Checking IPN response is valid' );
// Get received values from post data.
$validate_ipn = wp_unslash( $_POST ); // WPCS: CSRF ok, input var ok.
$validate_ipn['cmd'] = '_notify-validate';
// Send back post vars to paypal.
$params = array(
'body' => $validate_ipn,
'timeout' => 60,
'httpversion' => '1.1',
'compress' => false,
'decompress' => false,
'user-agent' => 'WooCommerce/' . WC()->version,
);
// Post back to get a response.
$response = wp_safe_remote_post( $this->sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr', $params );
WC_Gateway_Paypal::log( 'IPN Response: ' . wc_print_r( $response, true ) );
// Check to see if the request was valid.
if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 && strstr( $response['body'], 'VERIFIED' ) ) {
WC_Gateway_Paypal::log( 'Received valid response from PayPal IPN' );
return true;
}
WC_Gateway_Paypal::log( 'Received invalid response from PayPal IPN' );
if ( is_wp_error( $response ) ) {
WC_Gateway_Paypal::log( 'Error response: ' . $response->get_error_message() );
}
return false;
}
/**
* Check for a valid transaction type.
*
* @param string $txn_type Transaction type.
*/
protected function validate_transaction_type( $txn_type ) {
$accepted_types = array( 'cart', 'instant', 'express_checkout', 'web_accept', 'masspay', 'send_money', 'paypal_here' );
if ( ! in_array( strtolower( $txn_type ), $accepted_types, true ) ) {
WC_Gateway_Paypal::log( 'Aborting, Invalid type:' . $txn_type );
exit;
}
}
/**
* Check currency from IPN matches the order.
*
* @param WC_Order $order Order object.
* @param string $currency Currency code.
*/
protected function validate_currency( $order, $currency ) {
if ( $order->get_currency() !== $currency ) {
WC_Gateway_Paypal::log( 'Payment error: Currencies do not match (sent "' . $order->get_currency() . '" | returned "' . $currency . '")' );
/* translators: %s: currency code. */
$order->update_status( OrderStatus::ON_HOLD, sprintf( __( 'Validation error: PayPal currencies do not match (code %s).', 'woocommerce' ), $currency ) );
exit;
}
}
/**
* Check payment amount from IPN matches the order.
*
* @param WC_Order $order Order object.
* @param int $amount Amount to validate.
*/
protected function validate_amount( $order, $amount ) {
if ( number_format( $order->get_total(), 2, '.', '' ) !== number_format( $amount, 2, '.', '' ) ) {
WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (gross ' . $amount . ')' );
/* translators: %s: Amount. */
$order->update_status( OrderStatus::ON_HOLD, sprintf( __( 'Validation error: PayPal amounts do not match (gross %s).', 'woocommerce' ), $amount ) );
exit;
}
}
/**
* Check receiver email from PayPal. If the receiver email in the IPN is different than what is stored in.
* WooCommerce -> Settings -> Checkout -> PayPal, it will log an error about it.
*
* @param WC_Order $order Order object.
* @param string $receiver_email Email to validate.
*/
protected function validate_receiver_email( $order, $receiver_email ) {
if ( strcasecmp( trim( $receiver_email ), trim( $this->receiver_email ) ) !== 0 ) {
WC_Gateway_Paypal::log( "IPN Response is for another account: {$receiver_email}. Your email is {$this->receiver_email}" );
/* translators: %s: email address . */
$order->update_status( OrderStatus::ON_HOLD, sprintf( __( 'Validation error: PayPal IPN response from a different email address (%s).', 'woocommerce' ), $receiver_email ) );
exit;
}
}
/**
* Handle a completed payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_completed( $order, $posted ) {
if ( $order->has_status( wc_get_is_paid_statuses() ) ) {
WC_Gateway_Paypal::log( 'Aborting, Order #' . $order->get_id() . ' is already complete.' );
exit;
}
$this->validate_transaction_type( $posted['txn_type'] );
$this->validate_currency( $order, $posted['mc_currency'] );
$this->validate_amount( $order, $posted['mc_gross'] );
$this->validate_receiver_email( $order, $posted['receiver_email'] );
$this->save_paypal_meta_data( $order, $posted );
if ( OrderStatus::COMPLETED === $posted['payment_status'] ) {
if ( $order->has_status( OrderStatus::CANCELLED ) ) {
$this->payment_status_paid_cancelled_order( $order, $posted );
}
if ( ! empty( $posted['mc_fee'] ) ) {
$order->add_meta_data( 'PayPal Transaction Fee', wc_clean( $posted['mc_fee'] ) );
}
$this->payment_complete( $order, ( ! empty( $posted['txn_id'] ) ? wc_clean( $posted['txn_id'] ) : '' ), __( 'IPN payment completed', 'woocommerce' ) );
} else {
if ( 'authorization' === $posted['pending_reason'] ) {
$this->payment_on_hold( $order, __( 'Payment authorized. Change payment status to processing or complete to capture funds.', 'woocommerce' ) );
} else {
/* translators: %s: pending reason. */
$this->payment_on_hold( $order, sprintf( __( 'Payment pending (%s).', 'woocommerce' ), $posted['pending_reason'] ) );
}
}
}
/**
* Handle a pending payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_pending( $order, $posted ) {
$this->payment_status_completed( $order, $posted );
}
/**
* Handle a failed payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_failed( $order, $posted ) {
/* translators: %s: payment status. */
$order->update_status( OrderStatus::FAILED, sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), wc_clean( $posted['payment_status'] ) ) );
}
/**
* Handle a denied payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_denied( $order, $posted ) {
$this->payment_status_failed( $order, $posted );
}
/**
* Handle an expired payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_expired( $order, $posted ) {
$this->payment_status_failed( $order, $posted );
}
/**
* Handle a voided payment.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_voided( $order, $posted ) {
$this->payment_status_failed( $order, $posted );
}
/**
* When a user cancelled order is marked paid.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_paid_cancelled_order( $order, $posted ) {
$this->send_ipn_email_notification(
/* translators: %s: order link. */
sprintf( __( 'Payment for cancelled order %s received', 'woocommerce' ), '<a class="link" href="' . esc_url( $order->get_edit_order_url() ) . '">' . $order->get_order_number() . '</a>' ),
/* translators: %s: order ID. */
sprintf( __( 'Order #%s has been marked paid by PayPal IPN, but was previously cancelled. Admin handling required.', 'woocommerce' ), $order->get_order_number() )
);
}
/**
* Handle a refunded order.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_refunded( $order, $posted ) {
// Only handle full refunds, not partial.
if ( $order->get_total() === wc_format_decimal( $posted['mc_gross'] * -1, wc_get_price_decimals() ) ) {
/* translators: %s: payment status. */
$order->update_status( OrderStatus::REFUNDED, sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), strtolower( $posted['payment_status'] ) ) );
$this->send_ipn_email_notification(
/* translators: %s: order link. */
sprintf( __( 'Payment for order %s refunded', 'woocommerce' ), '<a class="link" href="' . esc_url( $order->get_edit_order_url() ) . '">' . $order->get_order_number() . '</a>' ),
/* translators: %1$s: order ID, %2$s: reason code. */
sprintf( __( 'Order #%1$s has been marked as refunded - PayPal reason code: %2$s', 'woocommerce' ), $order->get_order_number(), $posted['reason_code'] )
);
}
}
/**
* Handle a reversal.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_reversed( $order, $posted ) {
/* translators: %s: payment status. */
$order->update_status( OrderStatus::ON_HOLD, sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), wc_clean( $posted['payment_status'] ) ) );
$this->send_ipn_email_notification(
/* translators: %s: order link. */
sprintf( __( 'Payment for order %s reversed', 'woocommerce' ), '<a class="link" href="' . esc_url( $order->get_edit_order_url() ) . '">' . $order->get_order_number() . '</a>' ),
/* translators: %1$s: order ID, %2$s: reason code. */
sprintf( __( 'Order #%1$s has been marked on-hold due to a reversal - PayPal reason code: %2$s', 'woocommerce' ), $order->get_order_number(), wc_clean( $posted['reason_code'] ) )
);
}
/**
* Handle a cancelled reversal.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function payment_status_canceled_reversal( $order, $posted ) {
$this->send_ipn_email_notification(
/* translators: %s: order link. */
sprintf( __( 'Reversal cancelled for order #%s', 'woocommerce' ), $order->get_order_number() ),
/* translators: %1$s: order ID, %2$s: order link. */
sprintf( __( 'Order #%1$s has had a reversal cancelled. Please check the status of payment and update the order status accordingly here: %2$s', 'woocommerce' ), $order->get_order_number(), esc_url( $order->get_edit_order_url() ) )
);
}
/**
* Save important data from the IPN to the order.
*
* @param WC_Order $order Order object.
* @param array $posted Posted data.
*/
protected function save_paypal_meta_data( $order, $posted ) {
if ( ! empty( $posted['payment_type'] ) ) {
$order->update_meta_data( 'Payment type', wc_clean( $posted['payment_type'] ) );
}
if ( ! empty( $posted['txn_id'] ) ) {
$order->set_transaction_id( wc_clean( $posted['txn_id'] ) );
}
if ( ! empty( $posted['payment_status'] ) ) {
$order->update_meta_data( PayPalConstants::PAYPAL_ORDER_META_STATUS, wc_clean( $posted['payment_status'] ) );
}
$order->save();
}
/**
* Send a notification to the user handling orders.
*
* @param string $subject Email subject.
* @param string $message Email message.
*/
protected function send_ipn_email_notification( $subject, $message ) {
$new_order_settings = get_option( 'woocommerce_new_order_settings', array() );
$mailer = WC()->mailer();
$message = $mailer->wrap_message( $subject, $message );
$woocommerce_paypal_settings = get_option( 'woocommerce_paypal_settings' );
if ( ! empty( $woocommerce_paypal_settings['ipn_notification'] ) && 'no' === $woocommerce_paypal_settings['ipn_notification'] ) {
return;
}
$mailer->send( ! empty( $new_order_settings['recipient'] ) ? $new_order_settings['recipient'] : get_option( 'admin_email' ), strip_tags( $subject ), $message );
}
}

View File

@@ -0,0 +1,138 @@
<?php
/**
* PayPal Notices Class
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices instead. This class will be removed in 11.0.0.
* @package WooCommerce\Gateways
*/
declare(strict_types=1);
use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Gateways\PayPal\Notices as PayPalNotices;
require_once __DIR__ . '/class-wc-gateway-paypal-helper.php';
/**
* Class WC_Gateway_Paypal_Notices.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices instead. This class will be removed in 11.0.0.
* @since 10.3.0
*/
class WC_Gateway_Paypal_Notices {
/**
* The delegated notices instance.
*
* @var PayPalNotices
*/
private $notices;
/**
* Constructor.
*/
public function __construct() {
$this->notices = new PayPalNotices();
}
/**
* Add PayPal Standard notices.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_notices() instead.
* @since 10.4.0
* @return void
*/
public function add_paypal_notices() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_notices()' );
$this->notices->add_paypal_notices();
}
/**
* Add PayPal notices on the payments settings page.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_notices_on_payments_settings_page() instead.
* @since 10.4.0
* @return void
*/
public function add_paypal_notices_on_payments_settings_page() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_notices_on_payments_settings_page()' );
$this->notices->add_paypal_notices_on_payments_settings_page();
}
/**
* Add notice warning about the migration to PayPal Payments on the Payments settings page.
*
* @deprecated 10.4.0 No longer used. Functionality is now handled by add_paypal_notices_on_payments_settings_page().
* @return void
*/
public function add_paypal_migration_notice_on_payments_settings_page() {
wc_deprecated_function( __METHOD__, '10.4.0', 'WC_Gateway_Paypal_Notices::add_paypal_notices_on_payments_settings_page' );
$this->add_paypal_notices_on_payments_settings_page();
}
/**
* Add notice warning about the migration to PayPal Payments.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_migration_notice() instead.
* @since 10.3.0
* @return void
*/
public function add_paypal_migration_notice() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::add_paypal_migration_notice()' );
$this->notices->add_paypal_migration_notice();
}
/**
* Check if the installation notice has been dismissed.
*
* @deprecated 10.4.0 No longer used. Functionality is now handled by is_notice_dismissed().
* @return bool
*/
protected static function paypal_migration_notice_dismissed(): bool {
wc_deprecated_function( __METHOD__, '10.4.0', 'WC_Gateway_Paypal_Notices::is_notice_dismissed' );
return (bool) get_user_meta( get_current_user_id(), 'dismissed_paypal_migration_completed_notice', true );
}
/**
* Set the flag indicating PayPal account restriction.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::set_account_restriction_flag() instead.
* @since 10.4.0
* @return void
*/
public static function set_account_restriction_flag(): void {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::set_account_restriction_flag()' );
PayPalNotices::set_account_restriction_flag();
}
/**
* Clear the flag indicating PayPal account restriction.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::clear_account_restriction_flag() instead.
* @since 10.4.0
* @return void
*/
public static function clear_account_restriction_flag(): void {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::clear_account_restriction_flag()' );
PayPalNotices::clear_account_restriction_flag();
}
/**
* Handle PayPal order response to manage account restriction notices.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Notices::manage_account_restriction_flag_for_notice() instead.
* @since 10.4.0
* @param int|string $http_code The HTTP status code from the PayPal API response.
* @param array $response_data The decoded response data from the PayPal API.
* @param WC_Order $order The WooCommerce order object.
* @return void
*/
public static function manage_account_restriction_flag_for_notice( $http_code, array $response_data, WC_Order $order ): void {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Notices::manage_account_restriction_flag_for_notice()' );
PayPalNotices::manage_account_restriction_flag_for_notice( $http_code, $response_data, $order );
}
}

View File

@@ -0,0 +1,192 @@
<?php
/**
* Class WC_Gateway_Paypal_PDT_Handler file.
*
* @package WooCommerce\Gateways
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Enums\OrderStatus;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
require_once dirname( __FILE__ ) . '/class-wc-gateway-paypal-response.php';
/**
* Handle PDT Responses from PayPal.
*/
class WC_Gateway_Paypal_PDT_Handler extends WC_Gateway_Paypal_Response {
/**
* Identity token for PDT support
*
* @var string
*/
protected $identity_token;
/**
* Receiver email address to validate.
*
* @var string Receiver email address.
*/
protected $receiver_email;
/**
* Constructor.
*
* @param bool $sandbox Whether to use sandbox mode or not.
* @param string $identity_token Identity token for PDT support.
*/
public function __construct( $sandbox = false, $identity_token = '' ) {
add_action( 'woocommerce_thankyou_paypal', array( $this, 'check_response_for_order' ) );
$this->identity_token = $identity_token;
$this->sandbox = $sandbox;
}
/**
* Set receiver email to enable more strict validation.
*
* @param string $receiver_email Email to receive PDT notification from.
*/
public function set_receiver_email( $receiver_email = '' ) {
$this->receiver_email = $receiver_email;
}
/**
* Validate a PDT transaction to ensure its authentic.
*
* @param string $transaction TX ID.
* @return bool|array False or result array if successful and valid.
*/
protected function validate_transaction( $transaction ) {
$pdt = array(
'body' => array(
'cmd' => '_notify-synch',
'tx' => $transaction,
'at' => $this->identity_token,
),
'timeout' => 60,
'httpversion' => '1.1',
'user-agent' => 'WooCommerce/' . Constants::get_constant( 'WC_VERSION' ),
);
// Post back to get a response.
$response = wp_safe_remote_post( $this->sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr', $pdt );
if ( is_wp_error( $response ) || strpos( $response['body'], 'SUCCESS' ) !== 0 ) {
return false;
}
// Parse transaction result data.
$transaction_result = array_map( 'wc_clean', array_map( 'urldecode', explode( "\n", $response['body'] ) ) );
$transaction_results = array();
foreach ( $transaction_result as $line ) {
$line = explode( '=', $line );
$transaction_results[ $line[0] ] = isset( $line[1] ) ? $line[1] : '';
}
if ( ! empty( $transaction_results['charset'] ) && function_exists( 'iconv' ) ) {
foreach ( $transaction_results as $key => $value ) {
$transaction_results[ $key ] = iconv( $transaction_results['charset'], 'utf-8', $value );
}
}
return $transaction_results;
}
/**
* Check Response for PDT, taking the order id from the request.
*
* @deprecated 6.4 Use check_response_for_order instead.
*/
public function check_response() {
global $wp;
$order_id = apply_filters( 'woocommerce_thankyou_order_id', absint( $wp->query_vars['order-received'] ) );
$this->check_response_for_order( $order_id );
}
/**
* Check Response for PDT.
*
* @since 6.4
*
* @param mixed $wc_order_id The order id to check the response against.
*/
public function check_response_for_order( $wc_order_id ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $_REQUEST['tx'] ) ) {
return;
}
$wc_order = wc_get_order( $wc_order_id );
if ( ! $wc_order->needs_payment() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$transaction = wc_clean( wp_unslash( $_REQUEST['tx'] ) );
$transaction_result = $this->validate_transaction( $transaction );
if ( $transaction_result ) {
$status = strtolower( $transaction_result['payment_status'] );
$amount = isset( $transaction_result['mc_gross'] ) ? $transaction_result['mc_gross'] : 0;
$order = $this->get_paypal_order( $transaction_result['custom'] );
if ( ! $order ) {
// No valid WC order found on tx data.
return;
}
if ( $wc_order->get_id() !== $order->get_id() ) {
/* translators: 1: order ID, 2: order ID. */
WC_Gateway_Paypal::log( sprintf( __( 'Received PDT notification for order %1$d on endpoint for order %2$d.', 'woocommerce' ), $order->get_id(), $wc_order_id ), 'error' );
return;
}
if ( 0 !== strcasecmp( trim( $transaction_result['receiver_email'] ), trim( $this->receiver_email ) ) ) {
/* translators: 1: email address, 2: order ID . */
WC_Gateway_Paypal::log( sprintf( __( 'Received PDT notification for another account: %1$s. Order ID: %2$d.', 'woocommerce' ), $transaction_result['receiver_email'], $order->get_id() ), 'error' );
return;
}
// We have a valid response from PayPal.
WC_Gateway_Paypal::log( 'PDT Transaction Status: ' . wc_print_r( $status, true ) );
$order->add_meta_data( PayPalConstants::PAYPAL_ORDER_META_STATUS, $status );
$order->set_transaction_id( $transaction );
if ( OrderStatus::COMPLETED === $status ) {
if ( number_format( $order->get_total(), 2, '.', '' ) !== number_format( $amount, 2, '.', '' ) ) {
WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (amt ' . $amount . ')', 'error' );
/* translators: 1: Payment amount */
$this->payment_on_hold( $order, sprintf( __( 'Validation error: PayPal amounts do not match (amt %s).', 'woocommerce' ), $amount ) );
} else {
// Log paypal transaction fee and payment type.
if ( ! empty( $transaction_result['mc_fee'] ) ) {
$order->add_meta_data( 'PayPal Transaction Fee', wc_clean( $transaction_result['mc_fee'] ) );
}
if ( ! empty( $transaction_result['payment_type'] ) ) {
$order->add_meta_data( 'Payment type', wc_clean( $transaction_result['payment_type'] ) );
}
$this->payment_complete( $order, $transaction, __( 'PDT payment completed', 'woocommerce' ) );
}
} else {
if ( 'authorization' === $transaction_result['pending_reason'] ) {
$this->payment_on_hold( $order, __( 'Payment authorized. Change payment status to processing or complete to capture funds.', 'woocommerce' ) );
} else {
/* translators: 1: Pending reason */
$this->payment_on_hold( $order, sprintf( __( 'Payment pending (%s).', 'woocommerce' ), $transaction_result['pending_reason'] ) );
}
}
} else {
WC_Gateway_Paypal::log( 'Received invalid response from PayPal PDT' );
}
}
}

View File

@@ -0,0 +1,690 @@
<?php
/**
* Class WC_Gateway_Paypal_Request file.
*
* @package WooCommerce\Gateways
*/
declare(strict_types=1);
use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
use Automattic\WooCommerce\Gateways\PayPal\Request as PayPalRequest;
use Automattic\WooCommerce\Utilities\NumberUtil;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Generates requests to send to PayPal.
*/
class WC_Gateway_Paypal_Request {
/**
* Stores line items to send to PayPal.
*
* @var array
*/
protected $line_items = array();
/**
* Pointer to gateway making the request.
*
* @var WC_Gateway_Paypal
*/
protected $gateway;
/**
* Endpoint for requests from PayPal.
*
* @var string
*/
protected $notify_url;
/**
* Endpoint for requests to PayPal.
*
* @var string
*/
protected $endpoint;
/**
* The delegated request instance.
*
* @var PayPalRequest
*/
private $request;
/**
* Constructor.
*
* @param WC_Gateway_Paypal $gateway Paypal gateway object.
*/
public function __construct( $gateway ) {
$this->gateway = $gateway;
$this->notify_url = WC()->api_request_url( 'WC_Gateway_Paypal' );
$this->request = new PayPalRequest( $gateway );
}
/**
* Get the PayPal request URL for an order.
*
* @param WC_Order $order Order object.
* @param bool $sandbox Whether to use sandbox mode or not.
* @return string
*/
public function get_request_url( $order, $sandbox = false ) {
$this->endpoint = $sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' : 'https://www.paypal.com/cgi-bin/webscr?';
$paypal_args = $this->get_paypal_args( $order );
$paypal_args['bn'] = 'WooThemes_Cart'; // Append WooCommerce PayPal Partner Attribution ID. This should not be overridden for this gateway.
// Mask (remove) PII from the logs.
$mask = array(
'first_name' => '***',
'last_name' => '***',
'address1' => '***',
'address2' => '***',
'city' => '***',
'state' => '***',
'zip' => '***',
'country' => '***',
'email' => '***@***',
'night_phone_a' => '***',
'night_phone_b' => '***',
'night_phone_c' => '***',
);
WC_Gateway_Paypal::log( 'PayPal Request Args for order ' . $order->get_order_number() . ': ' . wc_print_r( array_merge( $paypal_args, array_intersect_key( $mask, $paypal_args ) ), true ) );
return $this->endpoint . http_build_query( $paypal_args, '', '&' );
}
/**
* Create a PayPal order using the Orders v2 API.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::create_paypal_order() instead. This method will be removed in 11.0.0.
* @param WC_Order $order Order object.
* @param string $payment_source The payment source.
* @param array $js_sdk_params Extra parameters for a PayPal JS SDK (Buttons) request.
* @return array|null
* @throws Exception If the PayPal order creation fails.
*/
public function create_paypal_order(
$order,
$payment_source = PayPalConstants::PAYMENT_SOURCE_PAYPAL,
$js_sdk_params = array()
) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::create_paypal_order()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
return $this->request->create_paypal_order( $order, $payment_source, $js_sdk_params );
}
/**
* Get PayPal order details.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::get_paypal_order_details() instead. This method will be removed in 11.0.0.
* @param string $paypal_order_id The ID of the PayPal order.
* @return array
* @throws Exception If the PayPal order details request fails.
* @throws Exception If the PayPal order details are not found.
*/
public function get_paypal_order_details( $paypal_order_id ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::get_paypal_order_details()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
return $this->request->get_paypal_order_details( $paypal_order_id );
}
/**
* This method authorizes or captures a PayPal payment and updates the order status.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::authorize_or_capture_payment() instead.
* @param WC_Order $order Order object.
* @param string|null $action_url The URL to authorize or capture the payment.
* @param string $action The action to perform. Either 'authorize' or 'capture'.
* @return void
* @throws Exception If the PayPal payment authorization or capture fails.
*/
public function authorize_or_capture_payment( $order, $action_url, $action = PayPalConstants::PAYMENT_ACTION_CAPTURE ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::authorize_or_capture_payment()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
$this->request->authorize_or_capture_payment( $order, $action_url, $action );
}
/**
* Capture a PayPal payment that has been authorized.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::capture_authorized_payment() instead. This method will be removed in 11.0.0.
* @param WC_Order $order Order object.
* @return void
* @throws Exception If the PayPal payment capture fails.
*/
public function capture_authorized_payment( $order ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::capture_authorized_payment()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
$this->request->capture_authorized_payment( $order );
}
/**
* Get the amount data for the PayPal order purchase unit field.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::get_paypal_order_purchase_unit_amount() instead. This method will be removed in 11.0.0.
* @param WC_Order $order Order object.
* @return array
*/
public function get_paypal_order_purchase_unit_amount( $order ) {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::get_paypal_order_purchase_unit_amount()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
return $this->request->get_paypal_order_purchase_unit_amount( $order );
}
/**
* Fetch the PayPal client-id from the Transact platform.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Request::fetch_paypal_client_id() instead. This method will be removed in 11.0.0.
* @return string|null The PayPal client-id, or null if the request fails.
* @throws Exception If the request fails.
*/
public function fetch_paypal_client_id() {
wc_deprecated_function( __METHOD__, '10.5.0', 'Automattic\WooCommerce\Gateways\PayPal\Request::fetch_paypal_client_id()' );
if ( ! $this->request ) {
$this->request = new PayPalRequest( $this->gateway );
}
return $this->request->fetch_paypal_client_id();
}
/**
* Limit length of an arg.
*
* @param string $string Argument to limit.
* @param integer $limit Limit size in characters.
* @return string
*/
protected function limit_length( $string, $limit = 127 ) {
$str_limit = $limit - 3;
if ( function_exists( 'mb_strimwidth' ) ) {
if ( mb_strlen( $string ) > $limit ) {
$string = mb_strimwidth( $string, 0, $str_limit ) . '...';
}
} elseif ( strlen( $string ) > $limit ) {
$string = substr( $string, 0, $str_limit ) . '...';
}
return $string;
}
/**
* Get transaction args for paypal request, except for line item args.
*
* @param WC_Order $order Order object.
* @return array
*/
protected function get_transaction_args( $order ) {
return array_merge(
array(
'cmd' => '_cart',
'business' => $this->gateway->get_option( 'email' ),
'no_note' => 1,
'currency_code' => get_woocommerce_currency(),
'charset' => 'utf-8',
'rm' => is_ssl() ? 2 : 1,
'upload' => 1,
'return' => esc_url_raw( add_query_arg( 'utm_nooverride', '1', $this->gateway->get_return_url( $order ) ) ),
'cancel_return' => esc_url_raw( $order->get_cancel_order_url_raw() ),
'image_url' => esc_url_raw( $this->gateway->get_option( 'image_url' ) ),
'paymentaction' => $this->gateway->get_option( 'paymentaction' ),
'invoice' => $this->limit_length( $this->gateway->get_option( 'invoice_prefix' ) . $order->get_order_number(), PayPalConstants::PAYPAL_INVOICE_ID_MAX_LENGTH ),
'custom' => wp_json_encode(
array(
'order_id' => $order->get_id(),
'order_key' => $order->get_order_key(),
)
),
'notify_url' => $this->limit_length( $this->notify_url, 255 ),
'first_name' => $this->limit_length( $order->get_billing_first_name(), 32 ),
'last_name' => $this->limit_length( $order->get_billing_last_name(), 64 ),
'address1' => $this->limit_length( $order->get_billing_address_1(), 100 ),
'address2' => $this->limit_length( $order->get_billing_address_2(), 100 ),
'city' => $this->limit_length( $order->get_billing_city(), 40 ),
'state' => $this->get_paypal_state( $order->get_billing_country(), $order->get_billing_state() ),
'zip' => $this->limit_length( wc_format_postcode( $order->get_billing_postcode(), $order->get_billing_country() ), 32 ),
'country' => $this->limit_length( $order->get_billing_country(), 2 ),
'email' => $this->limit_length( $order->get_billing_email() ),
),
$this->get_phone_number_args( $order ),
$this->get_shipping_args( $order )
);
}
/**
* If the default request with line items is too long, generate a new one with only one line item.
*
* If URL is longer than 2,083 chars, ignore line items and send cart to Paypal as a single item.
* One item's name can only be 127 characters long, so the URL should not be longer than limit.
* URL character limit via:
* https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer.
*
* @param WC_Order $order Order to be sent to Paypal.
* @param array $paypal_args Arguments sent to Paypal in the request.
* @return array
*/
protected function fix_request_length( $order, $paypal_args ) {
$max_paypal_length = 2083;
$query_candidate = http_build_query( $paypal_args, '', '&' );
if ( strlen( $this->endpoint . $query_candidate ) <= $max_paypal_length ) {
return $paypal_args;
}
return apply_filters(
'woocommerce_paypal_args',
array_merge(
$this->get_transaction_args( $order ),
$this->get_line_item_args( $order, true )
),
$order
);
}
/**
* Get PayPal Args for passing to PP.
*
* @param WC_Order $order Order object.
* @return array
*/
protected function get_paypal_args( $order ) {
WC_Gateway_Paypal::log( 'Generating payment form for order ' . $order->get_order_number() . '. Notify URL: ' . $this->notify_url );
$force_one_line_item = apply_filters( 'woocommerce_paypal_force_one_line_item', false, $order );
if ( ( wc_tax_enabled() && wc_prices_include_tax() ) || ! $this->line_items_valid( $order ) ) {
$force_one_line_item = true;
}
$paypal_args = apply_filters(
'woocommerce_paypal_args',
array_merge(
$this->get_transaction_args( $order ),
$this->get_line_item_args( $order, $force_one_line_item )
),
$order
);
return $this->fix_request_length( $order, $paypal_args );
}
/**
* Get phone number args for paypal request.
*
* @param WC_Order $order Order object.
* @return array
*/
protected function get_phone_number_args( $order ) {
$phone_number = wc_sanitize_phone_number( $order->get_billing_phone() );
if ( in_array( $order->get_billing_country(), array( 'US', 'CA' ), true ) ) {
$phone_number = ltrim( $phone_number, '+1' );
$phone_args = array(
'night_phone_a' => substr( $phone_number, 0, 3 ),
'night_phone_b' => substr( $phone_number, 3, 3 ),
'night_phone_c' => substr( $phone_number, 6, 4 ),
);
} else {
$calling_code = WC()->countries->get_country_calling_code( $order->get_billing_country() );
$calling_code = is_array( $calling_code ) ? $calling_code[0] : $calling_code;
if ( $calling_code ) {
$phone_number = str_replace( $calling_code, '', preg_replace( '/^0/', '', $order->get_billing_phone() ) );
}
$phone_args = array(
'night_phone_a' => $calling_code,
'night_phone_b' => $phone_number,
);
}
return $phone_args;
}
/**
* Get shipping args for paypal request.
*
* @param WC_Order $order Order object.
* @return array
*/
protected function get_shipping_args( $order ) {
$shipping_args = array();
if ( $order->needs_shipping_address() ) {
$shipping_args['address_override'] = $this->gateway->get_option( 'address_override' ) === 'yes' ? 1 : 0;
$shipping_args['no_shipping'] = 0;
if ( 'yes' === $this->gateway->get_option( 'send_shipping' ) ) {
// If we are sending shipping, send shipping address instead of billing.
$shipping_args['first_name'] = $this->limit_length( $order->get_shipping_first_name(), 32 );
$shipping_args['last_name'] = $this->limit_length( $order->get_shipping_last_name(), 64 );
$shipping_args['address1'] = $this->limit_length( $order->get_shipping_address_1(), 100 );
$shipping_args['address2'] = $this->limit_length( $order->get_shipping_address_2(), 100 );
$shipping_args['city'] = $this->limit_length( $order->get_shipping_city(), 40 );
$shipping_args['state'] = $this->get_paypal_state( $order->get_shipping_country(), $order->get_shipping_state() );
$shipping_args['country'] = $this->limit_length( $order->get_shipping_country(), 2 );
$shipping_args['zip'] = $this->limit_length( wc_format_postcode( $order->get_shipping_postcode(), $order->get_shipping_country() ), 32 );
}
} else {
$shipping_args['no_shipping'] = 1;
}
return $shipping_args;
}
/**
* Get shipping cost line item args for paypal request.
*
* @param WC_Order $order Order object.
* @param bool $force_one_line_item Whether one line item was forced by validation or URL length.
* @return array
*/
protected function get_shipping_cost_line_item( $order, $force_one_line_item ) {
$line_item_args = array();
$shipping_total = $order->get_shipping_total();
if ( $force_one_line_item ) {
$shipping_total += $order->get_shipping_tax();
}
// Add shipping costs. Paypal ignores anything over 5 digits (999.99 is the max).
// We also check that shipping is not the **only** cost as PayPal won't allow payment
// if the items have no cost.
if ( $order->get_shipping_total() > 0 && $order->get_shipping_total() < 999.99 && $this->number_format( $order->get_shipping_total() + $order->get_shipping_tax(), $order ) !== $this->number_format( $order->get_total(), $order ) ) {
$line_item_args['shipping_1'] = $this->number_format( $shipping_total, $order );
} elseif ( $order->get_shipping_total() > 0 ) {
/* translators: %s: Order shipping method */
$this->add_line_item( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ), 1, $this->number_format( $shipping_total, $order ) );
}
return $line_item_args;
}
/**
* Get line item args for paypal request as a single line item.
*
* @param WC_Order $order Order object.
* @return array
*/
protected function get_line_item_args_single_item( $order ) {
$this->delete_line_items();
$all_items_name = $this->get_order_item_names( $order );
$this->add_line_item( $all_items_name ? $all_items_name : __( 'Order', 'woocommerce' ), 1, $this->number_format( $order->get_total() - $this->round( $order->get_shipping_total() + $order->get_shipping_tax(), $order ), $order ), $order->get_order_number() );
$line_item_args = $this->get_shipping_cost_line_item( $order, true );
return array_merge( $line_item_args, $this->get_line_items() );
}
/**
* Get line item args for paypal request.
*
* @param WC_Order $order Order object.
* @param bool $force_one_line_item Create only one item for this order.
* @return array
*/
protected function get_line_item_args( $order, $force_one_line_item = false ) {
$line_item_args = array();
if ( $force_one_line_item ) {
/**
* Send order as a single item.
*
* For shipping, we longer use shipping_1 because paypal ignores it if *any* shipping rules are within paypal, and paypal ignores anything over 5 digits (999.99 is the max).
*/
$line_item_args = $this->get_line_item_args_single_item( $order );
} else {
/**
* Passing a line item per product if supported.
*/
$this->prepare_line_items( $order );
$line_item_args['tax_cart'] = $this->number_format( $order->get_total_tax(), $order );
if ( $order->get_total_discount() > 0 ) {
$line_item_args['discount_amount_cart'] = $this->number_format( $this->round( $order->get_total_discount(), $order ), $order );
}
$line_item_args = array_merge( $line_item_args, $this->get_shipping_cost_line_item( $order, false ) );
$line_item_args = array_merge( $line_item_args, $this->get_line_items() );
}
return $line_item_args;
}
/**
* Get order item names as a string.
*
* @param WC_Order $order Order object.
* @return string
*/
protected function get_order_item_names( $order ) {
$item_names = array();
foreach ( $order->get_items() as $item ) {
$item_name = $item->get_name();
$item_meta = wp_strip_all_tags(
wc_display_item_meta(
$item,
array(
'before' => '',
'separator' => ', ',
'after' => '',
'echo' => false,
'autop' => false,
)
)
);
if ( $item_meta ) {
$item_name .= ' (' . $item_meta . ')';
}
$item_names[] = $item_name . ' x ' . $item->get_quantity();
}
return apply_filters( 'woocommerce_paypal_get_order_item_names', implode( ', ', $item_names ), $order );
}
/**
* Get order item names as a string.
*
* @param WC_Order $order Order object.
* @param WC_Order_Item $item Order item object.
* @return string
*/
protected function get_order_item_name( $order, $item ) {
$item_name = $item->get_name();
$item_meta = wp_strip_all_tags(
wc_display_item_meta(
$item,
array(
'before' => '',
'separator' => ', ',
'after' => '',
'echo' => false,
'autop' => false,
)
)
);
if ( $item_meta ) {
$item_name .= ' (' . $item_meta . ')';
}
return apply_filters( 'woocommerce_paypal_get_order_item_name', $item_name, $order, $item );
}
/**
* Return all line items.
*/
protected function get_line_items() {
return $this->line_items;
}
/**
* Remove all line items.
*/
protected function delete_line_items() {
$this->line_items = array();
}
/**
* Check if the order has valid line items to use for PayPal request.
*
* The line items are invalid in case of mismatch in totals or if any amount < 0.
*
* @param WC_Order $order Order to be examined.
* @return bool
*/
protected function line_items_valid( $order ) {
$negative_item_amount = false;
$calculated_total = 0;
// Products.
foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item ) {
if ( 'fee' === $item['type'] ) {
$item_line_total = $this->number_format( $item['line_total'], $order );
$calculated_total += $item_line_total;
} else {
$item_line_total = $this->number_format( $order->get_item_subtotal( $item, false ), $order );
$calculated_total += $item_line_total * $item->get_quantity();
}
if ( $item_line_total < 0 ) {
$negative_item_amount = true;
}
}
$mismatched_totals = $this->number_format( $calculated_total + $order->get_total_tax() + $this->round( $order->get_shipping_total(), $order ) - $this->round( $order->get_total_discount(), $order ), $order ) !== $this->number_format( $order->get_total(), $order );
return ! $negative_item_amount && ! $mismatched_totals;
}
/**
* Get line items to send to paypal.
*
* @param WC_Order $order Order object.
*/
protected function prepare_line_items( $order ) {
$this->delete_line_items();
// Products.
foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item ) {
if ( 'fee' === $item['type'] ) {
$item_line_total = $this->number_format( $item['line_total'], $order );
$this->add_line_item( $item->get_name(), 1, $item_line_total );
} else {
$product = $item->get_product();
$sku = $product ? $product->get_sku() : '';
$item_line_total = $this->number_format( $order->get_item_subtotal( $item, false ), $order );
$this->add_line_item( $this->get_order_item_name( $order, $item ), $item->get_quantity(), $item_line_total, $sku );
}
}
}
/**
* Add PayPal Line Item.
*
* @param string $item_name Item name.
* @param int $quantity Item quantity.
* @param float $amount Amount.
* @param string $item_number Item number.
*/
protected function add_line_item( $item_name, $quantity = 1, $amount = 0.0, $item_number = '' ) {
$index = ( count( $this->line_items ) / 4 ) + 1;
$item = apply_filters(
'woocommerce_paypal_line_item',
array(
'item_name' => html_entity_decode( wc_trim_string( $item_name ? wp_strip_all_tags( $item_name ) : __( 'Item', 'woocommerce' ), 127 ), ENT_NOQUOTES, 'UTF-8' ),
'quantity' => (int) $quantity,
'amount' => wc_float_to_string( (float) $amount ),
'item_number' => $item_number,
),
$item_name,
$quantity,
$amount,
$item_number
);
$this->line_items[ 'item_name_' . $index ] = $this->limit_length( $item['item_name'], 127 );
$this->line_items[ 'quantity_' . $index ] = $item['quantity'];
$this->line_items[ 'amount_' . $index ] = $item['amount'];
$this->line_items[ 'item_number_' . $index ] = $this->limit_length( $item['item_number'], 127 );
}
/**
* Get the state to send to paypal.
*
* @param string $cc Country two letter code.
* @param string $state State code.
* @return string
*/
protected function get_paypal_state( $cc, $state ) {
if ( 'US' === $cc ) {
return $state;
}
$states = WC()->countries->get_states( $cc );
if ( isset( $states[ $state ] ) ) {
return $states[ $state ];
}
return $state;
}
/**
* Check if currency has decimals.
*
* @param string $currency Currency to check.
* @return bool
*/
protected function currency_has_decimals( $currency ) {
if ( in_array( $currency, array( 'HUF', 'JPY', 'TWD' ), true ) ) {
return false;
}
return true;
}
/**
* Round prices.
*
* @param double $price Price to round.
* @param WC_Order $order Order object.
* @return double
*/
protected function round( $price, $order ) {
$precision = 2;
if ( ! $this->currency_has_decimals( $order->get_currency() ) ) {
$precision = 0;
}
return NumberUtil::round( $price, $precision );
}
/**
* Format prices.
*
* @param float|int $price Price to format.
* @param WC_Order $order Order object.
* @return string
*/
protected function number_format( $price, $order ) {
$decimals = 2;
if ( ! $this->currency_has_decimals( $order->get_currency() ) ) {
$decimals = 0;
}
return number_format( (float) $price, $decimals, '.', '' );
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Class WC_Gateway_Paypal_Response file.
*
* @package WooCommerce\Gateways
*/
use Automattic\WooCommerce\Enums\OrderStatus;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles Responses.
*/
abstract class WC_Gateway_Paypal_Response {
/**
* Sandbox mode
*
* @var bool
*/
protected $sandbox = false;
/**
* Get the order from the PayPal 'Custom' variable.
*
* @param string $raw_custom JSON Data passed back by PayPal.
* @return bool|WC_Order object
*/
protected function get_paypal_order( $raw_custom ) {
// We have the data in the correct format, so get the order.
$custom = json_decode( $raw_custom );
if ( $custom && is_object( $custom ) ) {
$order_id = $custom->order_id;
$order_key = $custom->order_key;
} else {
// Nothing was found.
WC_Gateway_Paypal::log( 'Order ID and key were not found in "custom".', 'error' );
return false;
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
// We have an invalid $order_id, probably because invoice_prefix has changed.
$order_id = wc_get_order_id_by_order_key( $order_key );
$order = wc_get_order( $order_id );
}
if ( ! $order || ! hash_equals( $order->get_order_key(), $order_key ) ) {
WC_Gateway_Paypal::log( 'Order Keys do not match.', 'error' );
return false;
}
return $order;
}
/**
* Complete order, add transaction ID and note.
*
* @param WC_Order $order Order object.
* @param string $txn_id Transaction ID.
* @param string $note Payment note.
*/
protected function payment_complete( $order, $txn_id = '', $note = '' ) {
if ( ! $order->has_status( array( OrderStatus::PROCESSING, OrderStatus::COMPLETED ) ) ) {
$order->add_order_note( $note );
$order->payment_complete( $txn_id );
if ( isset( WC()->cart ) ) {
WC()->cart->empty_cart();
}
}
}
/**
* Hold order and add note.
*
* @param WC_Order $order Order object.
* @param string $reason Reason why the payment is on hold.
*/
protected function payment_on_hold( $order, $reason = '' ) {
$order->update_status( OrderStatus::ON_HOLD, $reason );
if ( isset( WC()->cart ) ) {
WC()->cart->empty_cart();
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Class WC_Gateway_Paypal_Transact_Account_Manager file.
*
* @package WooCommerce\Gateways
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager instead. This class will be removed in 11.0.0.
*/
declare(strict_types=1);
use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager as PayPalTransactAccountManager;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles Transact account management.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager instead. This class will be removed in 11.0.0.
*/
final class WC_Gateway_Paypal_Transact_Account_Manager {
/**
* The delegated TransactAccountManager instance.
*
* @var PayPalTransactAccountManager
*/
private $transact_account_manager;
/**
* Constructor.
*
* @param WC_Gateway_Paypal $gateway Paypal gateway object.
*/
public function __construct( WC_Gateway_Paypal $gateway ) {
$this->transact_account_manager = new PayPalTransactAccountManager( $gateway );
}
/**
* Onboard the merchant with the Transact platform.
*
* @return void
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager::do_onboarding() instead. This method will be removed in 11.0.0.
*/
public function do_onboarding() {
wc_deprecated_function(
__METHOD__,
'10.5.0',
PayPalTransactAccountManager::class . '::do_onboarding()'
);
$this->transact_account_manager->do_onboarding();
}
/**
* Get the Transact account (merchant or provider) data. Performs a fetch if the account
* is not in cache or expired.
*
* @param string $account_type The type of account to get (merchant or provider).
* @return array|null Returns null if the transact account cannot be retrieved.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager::get_transact_account_data() instead. This method will be removed in 11.0.0.
*/
public function get_transact_account_data( $account_type ) {
wc_deprecated_function(
__METHOD__,
'10.5.0',
PayPalTransactAccountManager::class . '::get_transact_account_data()'
);
return $this->transact_account_manager->get_transact_account_data( $account_type );
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Class WC_Gateway_Paypal_Webhook_Handler file.
*
* @package WooCommerce\Gateways
*
* @deprecated 10.5.0 Deprecated in favor of Automattic\WooCommerce\Gateways\PayPal\WebhookHandler
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Gateways\PayPal\WebhookHandler as PayPalWebhookHandler;
if ( ! class_exists( 'WC_Gateway_Paypal_Helper' ) ) {
require_once __DIR__ . '/class-wc-gateway-paypal-helper.php';
}
if ( ! class_exists( 'WC_Gateway_Paypal_Request' ) ) {
require_once __DIR__ . '/class-wc-gateway-paypal-request.php';
}
/**
* Handles webhook events.
*
* @deprecated 10.5.0 Deprecated in favor of Automattic\WooCommerce\Gateways\PayPal\WebhookHandler
*/
class WC_Gateway_Paypal_Webhook_Handler {
/**
* The delegated webhook handler instance.
*
* @var PayPalWebhookHandler
*/
private PayPalWebhookHandler $webhook_handler;
/**
* Constructor.
*/
public function __construct() {
$this->webhook_handler = new PayPalWebhookHandler();
}
/**
* Process the webhook event.
*
* @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\WebhookHandler::process_webhook() instead. This method will be removed in 11.0.0.
*
* @param WP_REST_Request $request The request object.
*
* @return void
*
* @deprecated 10.5.0 Deprecated in favor of Automattic\WooCommerce\Gateways\PayPal\WebhookHandler::process_webhook
*/
public function process_webhook( WP_REST_Request $request ) {
wc_deprecated_function(
__METHOD__,
'10.5.0',
PayPalWebhookHandler::class . '::process_webhook()'
);
$this->webhook_handler->process_webhook( $request );
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* Settings for PayPal Standard Gateway.
*
* @package WooCommerce\Classes\Payment
*/
declare(strict_types=1);
use Automattic\WooCommerce\Utilities\LoggingUtil;
defined( 'ABSPATH' ) || exit;
$settings = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal Standard', 'woocommerce' ),
'default' => 'no',
),
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'safe_text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => __( 'PayPal', 'woocommerce' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'woocommerce' ),
'type' => 'text',
'desc_tip' => true,
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce' ),
'default' => __( "Pay via PayPal; you can pay with your credit card if you don't have a PayPal account.", 'woocommerce' ),
),
'email' => array(
'title' => __( 'PayPal email', 'woocommerce' ),
'type' => 'email',
'description' => __( 'Please enter your PayPal email address; this is needed in order to take payment.', 'woocommerce' ),
'default' => get_option( 'admin_email' ),
'desc_tip' => true,
'placeholder' => 'you@youremail.com',
),
'advanced' => array(
'title' => __( 'Advanced options', 'woocommerce' ),
'type' => 'title',
'description' => '',
),
'testmode' => array(
'title' => __( 'PayPal sandbox', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal sandbox', 'woocommerce' ),
'default' => 'no',
/* translators: %s: URL */
'description' => sprintf( __( 'PayPal sandbox can be used to test payments. Sign up for a <a href="%s">developer account</a>.', 'woocommerce' ), 'https://developer.paypal.com/' ),
),
'paymentaction' => array(
'title' => __( 'Payment action', 'woocommerce' ),
'type' => 'select',
'class' => 'wc-enhanced-select',
'description' => __( 'Choose whether you wish to capture funds immediately or authorize payment only.', 'woocommerce' ),
'default' => 'sale',
'desc_tip' => true,
'options' => array(
'sale' => __( 'Capture', 'woocommerce' ),
'authorization' => __( 'Authorize', 'woocommerce' ),
),
),
'paypal_buttons' => array(
'title' => __( 'PayPal Buttons', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal Buttons', 'woocommerce' ),
'default' => 'yes',
'description' => __( 'Enable PayPal buttons to offer PayPal, Venmo and Pay Later as express checkout options on product, cart, and checkout pages.', 'woocommerce' ),
),
'invoice_prefix' => array(
'title' => __( 'Invoice prefix', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Please enter a prefix for your invoice numbers. If you use your PayPal account for multiple stores ensure this prefix is unique as PayPal will not allow orders with the same invoice number.', 'woocommerce' ),
'default' => 'WC-',
'desc_tip' => true,
),
'send_shipping' => array(
'title' => __( 'Shipping details', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Send shipping details to PayPal instead of billing.', 'woocommerce' ),
'description' => __( 'PayPal allows us to send one address. If you are using PayPal for shipping labels you may prefer to send the shipping address rather than billing. Turning this option off may prevent PayPal Seller protection from applying.', 'woocommerce' ),
'default' => 'yes',
),
'address_override' => array(
'title' => __( 'Address override', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Prevent buyers from changing the shipping address.', 'woocommerce' ),
'description' => __( 'When enabled, PayPal will use the address provided by the checkout form, and prevent the buyer from changing it inside the PayPal payment page. Disable this to let buyers choose a shipping address from their PayPal account. PayPal verifies addresses therefore this setting can cause errors (we recommend keeping it disabled).', 'woocommerce' ),
'default' => 'no',
),
'debug' => array(
'title' => __( 'Debug log', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable logging', 'woocommerce' ),
'default' => 'no',
/* translators: %s: URL */
'description' => sprintf(
// translators: %s is a placeholder for a URL.
__( 'Log PayPal events such as IPN requests and review them on the <a href="%s">Logs screen</a>. Note: this may log personal information. We recommend using this for debugging purposes only and deleting the logs when finished.', 'woocommerce' ),
esc_url( LoggingUtil::get_logs_tab_url() )
),
),
);
$legacy_settings = array(
'image_url' => array(
'title' => __( 'Image url', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Optionally enter the URL to a 150x50px image displayed as your logo in the upper left corner of the PayPal checkout pages.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'ipn_notification' => array(
'title' => __( 'IPN email notifications', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable IPN email notifications', 'woocommerce' ),
'default' => 'yes',
'description' => __( 'Send notifications when an IPN is received from PayPal indicating refunds, chargebacks and cancellations.', 'woocommerce' ),
'is_legacy' => true,
),
'receiver_email' => array(
'title' => __( 'Receiver email', 'woocommerce' ),
'type' => 'email',
'description' => __( 'If your main PayPal email differs from the PayPal email entered above, input your main receiver email for your PayPal account here. This is used to validate IPN requests.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => 'you@youremail.com',
'is_legacy' => true,
),
'identity_token' => array(
'title' => __( 'PayPal identity token', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Optionally enable "Payment Data Transfer" (Profile > Profile and Settings > My Selling Tools > Website Preferences) and then copy your identity token here. This will allow payments to be verified without the need for PayPal IPN.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => '',
'is_legacy' => true,
),
'api_details' => array(
'title' => __( 'API credentials', 'woocommerce' ),
'type' => 'title',
/* translators: %s: URL */
'description' => sprintf( __( 'Enter your PayPal API credentials to process refunds via PayPal. Learn how to access your <a href="%s">PayPal API Credentials</a>.', 'woocommerce' ), 'https://developer.paypal.com/webapps/developer/docs/classic/api/apiCredentials/#create-an-api-signature' ),
'is_legacy' => true,
),
'api_username' => array(
'title' => __( 'Live API username', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'api_password' => array(
'title' => __( 'Live API password', 'woocommerce' ),
'type' => 'password',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'api_signature' => array(
'title' => __( 'Live API signature', 'woocommerce' ),
'type' => 'password',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'sandbox_api_username' => array(
'title' => __( 'Sandbox API username', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'sandbox_api_password' => array(
'title' => __( 'Sandbox API password', 'woocommerce' ),
'type' => 'password',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
'sandbox_api_signature' => array(
'title' => __( 'Sandbox API signature', 'woocommerce' ),
'type' => 'password',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' ),
'is_legacy' => true,
),
);
return array_merge( $settings, $legacy_settings );