laipower/wp-content/plugins/easy-digital-downloads/includes/gateways/paypal/functions.php

330 lines
8.7 KiB
PHP

<?php
/**
* PayPal Commerce Functions
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal;
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
/**
* Determines whether or not there's a valid REST API connection.
*
* @param string $mode Mode to check (`live` or `sandbox`).
*
* @since 2.11
* @return bool
*/
function has_rest_api_connection( $mode = '' ) {
try {
$api = new API( $mode );
return true;
} catch ( Authentication_Exception $e ) {
return false;
}
}
/**
* Determines whether or not the account is ready to accept payments.
* Requirements:
*
* - API keys must be set.
* - Merchant account must be ready to accept payments.
*
* @see API::set_credentials()
* @see AccountStatusValidator::check_merchant_account()
*
* @since 2.11
*
* @param string $mode
*
* @return bool
*/
function ready_to_accept_payments( $mode = '' ) {
if ( empty( $mode ) ) {
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
}
if ( ! has_rest_api_connection( $mode ) ) {
return false;
}
$validator = new AccountStatusValidator( $mode );
$validator->check_merchant_account();
return empty( $validator->errors_for_merchant_account->errors );
}
/**
* Determines whether or not PayPal Standard should be enabled.
* This returns true if the store owner previously had a PayPal Standard connection but has not yet
* connected to the new REST API implementation.
*
* If PayPal Standard is enabled, then PayPal payments run through the legacy API.
*
* @param string $mode If omitted, current site mode is used.
*
* @since 2.11
* @return bool
*/
function paypal_standard_enabled( $mode = '' ) {
if ( empty( $mode ) ) {
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
}
$rest_connection = has_rest_api_connection( $mode );
$enabled = ! $rest_connection && edd_get_option( 'paypal_email' );
/**
* Filters whether or not PayPal Standard is enabled.
*
* @since 2.11
*
* @param bool $enabled
*/
return apply_filters( 'edd_paypal_standard_enabled', $enabled );
}
/**
* Returns the partner merchant ID for a given mode.
*
* @param string $mode If omitted, current site mode is used.
*
* @since 2.11
* @return string
*/
function get_partner_merchant_id( $mode = '' ) {
if ( empty( $mode ) ) {
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
}
if ( API::MODE_LIVE === $mode ) {
return EDD_PAYPAL_MERCHANT_ID;
} else {
return EDD_PAYPAL_SANDBOX_MERCHANT_ID;
}
}
/**
* Returns the styles used for the PayPal buttons.
*
* @return array
*/
function get_button_styles() {
$styles = array(
'layout' => 'vertical',
'size' => 'responsive',
'shape' => 'rect',
'color' => 'gold',
'label' => 'paypal'
);
if ( ! edd_is_checkout() ) {
$styles['layout'] = 'horizontal';
$styles['label'] = 'buynow';
}
/**
* Filters the button styles.
*
* @since 2.11
*/
return apply_filters( 'edd_paypal_smart_button_style', $styles );
}
/**
* Gets the PayPal purchase units without the individual item breakdown.
*
* @since 2.11.2
*
* @param int $payment_id The payment/order ID.
* @param array $purchase_data The array of purchase data.
* @param array $payment_args The array created to insert the payment into the database.
*
* @return array
*/
function get_order_purchase_units_without_breakdown( $payment_id, $purchase_data, $payment_args ) {
$order_amount = array(
'currency_code' => edd_get_currency(),
'value' => (string) edd_sanitize_amount( $purchase_data['price'] ),
);
if ( (float) $purchase_data['tax'] > 0 ) {
$order_amount['breakdown'] = array(
'item_total' => array(
'currency_code' => edd_get_currency(),
'value' => (string) edd_sanitize_amount( $purchase_data['price'] - $purchase_data['tax'] )
),
'tax_total' => array(
'currency_code' => edd_get_currency(),
'value' => (string) edd_sanitize_amount( $purchase_data['tax'] ),
)
);
}
return array(
'reference_id' => $payment_args['purchase_key'],
'amount' => $order_amount,
'custom_id' => $payment_id
);
}
/**
* Gets the PayPal purchase units. The order breakdown includes the order items, tax, and discount.
*
* @since 2.11.2
* @param int $payment_id The payment/order ID.
* @param array $purchase_data The array of purchase data.
* @param array $payment_args The array created to insert the payment into the database.
* @return array
*/
function get_order_purchase_units( $payment_id, $purchase_data, $payment_args ) {
$currency = edd_get_currency();
$order_subtotal = $purchase_data['subtotal'];
$items = get_order_items( $purchase_data );
// Adjust the order subtotal if any items are discounted.
foreach ( $items as &$item ) {
// A discount can be negative, so cast it to an absolute value for comparison.
if ( (float) abs( $item['discount'] ) > 0 ) {
$order_subtotal -= $item['discount'];
}
// The discount amount is not passed to PayPal as part of the $item.
unset( $item['discount'] );
}
$discount = 0;
// Fees which are not item specific need to be added to the PayPal data as order items.
if ( ! empty( $purchase_data['fees'] ) ) {
foreach ( $purchase_data['fees'] as $fee ) {
if ( ! empty( $fee['download_id'] ) ) {
continue;
}
// Positive fees.
if ( floatval( $fee['amount'] ) > 0 ) {
$items[] = array(
'name' => stripslashes_deep( html_entity_decode( wp_strip_all_tags( $fee['label'] ), ENT_COMPAT, 'UTF-8' ) ),
'unit_amount' => array(
'currency_code' => $currency,
'value' => (string) edd_sanitize_amount( $fee['amount'] ),
),
'quantity' => 1,
);
$order_subtotal += abs( $fee['amount'] );
} else {
// This is a negative fee (discount) not assigned to a specific Download
$discount += abs( $fee['amount'] );
}
}
}
$order_amount = array(
'currency_code' => $currency,
'value' => (string) edd_sanitize_amount( $purchase_data['price'] ),
'breakdown' => array(
'item_total' => array(
'currency_code' => $currency,
'value' => (string) edd_sanitize_amount( $order_subtotal ),
),
),
);
$tax = (float) $purchase_data['tax'] > 0 ? $purchase_data['tax'] : 0;
if ( $tax > 0 ) {
$order_amount['breakdown']['tax_total'] = array(
'currency_code' => $currency,
'value' => (string) edd_sanitize_amount( $tax ),
);
}
// This is only added by negative global fees.
if ( $discount > 0 ) {
$order_amount['breakdown']['discount'] = array(
'currency_code' => $currency,
'value' => (string) edd_sanitize_amount( $discount ),
);
}
return array(
wp_parse_args( array(
'amount' => $order_amount,
'items' => $items
), get_order_purchase_units_without_breakdown( $payment_id, $purchase_data, $payment_args ) )
);
}
/**
* Gets an array of order items, formatted for PayPal, from the $purchase_data.
*
* @since 2.11.2
* @param array $purchase_data
* @return array
*/
function get_order_items( $purchase_data ) {
// Create an array of items for the order.
$items = array();
if ( ! is_array( $purchase_data['cart_details'] ) || empty( $purchase_data['cart_details'] ) ) {
return $items;
}
$i = 0;
foreach ( $purchase_data['cart_details'] as $item ) {
$item_amount = ( $item['subtotal'] / $item['quantity'] ) - ( $item['discount'] / $item['quantity'] );
if ( $item_amount <= 0 ) {
$item_amount = 0;
}
$items[ $i ] = array(
'name' => stripslashes_deep( html_entity_decode( substr( edd_get_cart_item_name( $item ), 0, 127 ), ENT_COMPAT, 'UTF-8' ) ),
'quantity' => $item['quantity'],
'unit_amount' => array(
'currency_code' => edd_get_currency(),
'value' => (string) edd_sanitize_amount( $item_amount ),
),
'discount' => $item['discount'], // This is unset later and never sent to PayPal.
);
if ( edd_use_skus() ) {
$sku = edd_get_download_sku( $item['id'] );
if ( ! empty( $sku ) && '-' !== $sku ) {
$items[ $i ]['sku'] = $sku;
}
}
$i++;
}
return $items;
}
/**
* Attempts to detect if there's an item total mismatch. This means the individual item breakdowns don't
* add up to our proposed totals.
*
* @link https://github.com/easydigitaldownloads/easy-digital-downloads/pull/8835#issuecomment-921759101
* @internal Not intended for public use.
*
* @since 2.11.2
*
* @param object $response
*
* @return bool
*/
function _is_item_total_mismatch( $response ) {
if ( ! isset( $response->details ) || ! is_array( $response->details ) ) {
return false;
}
foreach( $response->details as $detail ) {
if ( ! empty( $detail->issue ) && 'ITEM_TOTAL_MISMATCH' === strtoupper( $detail->issue ) ) {
return true;
}
}
return false;
}