File "PurchaseUnitSanitizer.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/woocommerce-paypal-payments/modules/ppcp-api-client/src/Helper/PurchaseUnitSanitizer.php
File size: 9.18 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Class PurchaseUnitSanitizer.
*
* Sanitizes a purchase_unit array to be consumed by PayPal.
*
* All money values send to PayPal can only have 2 decimal points. WooCommerce internally does
* not have this restriction. Therefore, the totals of the cart in WooCommerce and the totals
* of the rounded money values of the items, we send to PayPal, can differ. In those case we either:
* - Add an extra line with roundings.
* - Don't send the line items.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
/**
* Class PurchaseUnitSanitizer
*/
class PurchaseUnitSanitizer {
const MODE_DITCH = 'ditch';
const MODE_EXTRA_LINE = 'extra_line';
const VALID_MODES = array(
self::MODE_DITCH,
self::MODE_EXTRA_LINE,
);
const EXTRA_LINE_NAME = 'Subtotal mismatch';
/**
* The purchase unit data
*
* @var array
*/
private $purchase_unit = array();
/**
* Whether to allow items to be ditched.
*
* @var bool
*/
private $allow_ditch_items = true;
/**
* The working mode
*
* @var string
*/
private $mode;
/**
* The name for the extra line
*
* @var string
*/
private $extra_line_name;
/**
* The last message. To be added to order notes.
*
* @var string
*/
private $last_message = '';
/**
* If the items and breakdown has been ditched.
*
* @var bool
*/
private $has_ditched_items_breakdown = false;
/**
* PurchaseUnitSanitizer constructor.
*
* @param string|null $mode The mismatch handling mode, ditch or extra_line.
* @param string|null $extra_line_name The name of the extra line.
*/
public function __construct( string $mode = null, string $extra_line_name = null ) {
if ( ! in_array( $mode, self::VALID_MODES, true ) ) {
$mode = self::MODE_DITCH;
}
if ( ! $extra_line_name ) {
$extra_line_name = self::EXTRA_LINE_NAME;
}
$this->mode = $mode;
$this->extra_line_name = $extra_line_name;
}
/**
* The purchase_unit amount.
*
* @return array
*/
private function amount(): array {
return $this->purchase_unit['amount'] ?? array();
}
/**
* The purchase_unit currency code.
*
* @return string
*/
private function currency_code(): string {
return (string) ( $this->amount()['currency_code'] ?? '' );
}
/**
* The purchase_unit breakdown.
*
* @return array
*/
private function breakdown(): array {
return $this->amount()['breakdown'] ?? array();
}
/**
* The purchase_unit breakdown.
*
* @param string $key The breakdown element to get the value from.
* @return float
*/
private function breakdown_value( string $key ): float {
if ( ! isset( $this->breakdown()[ $key ] ) ) {
return 0.0;
}
return (float) ( $this->breakdown()[ $key ]['value'] ?? 0.0 );
}
/**
* The purchase_unit items array.
*
* @return array
*/
private function items(): array {
return $this->purchase_unit['items'] ?? array();
}
/**
* The sanitizes the purchase_unit array.
*
* @param array $purchase_unit The purchase_unit array that should be sanitized.
* @param bool $allow_ditch_items Whether to allow items to be ditched.
* @return array
*/
public function sanitize( array $purchase_unit, bool $allow_ditch_items = true ): array {
$this->purchase_unit = $purchase_unit;
$this->allow_ditch_items = $allow_ditch_items;
$this->has_ditched_items_breakdown = false;
$this->sanitize_item_amount_mismatch();
$this->sanitize_item_tax_mismatch();
$this->sanitize_breakdown_mismatch();
return $this->purchase_unit;
}
/**
* The sanitizes the purchase_unit items amount.
*
* @return void
*/
private function sanitize_item_amount_mismatch(): void {
$item_mismatch = $this->calculate_item_mismatch();
if ( $this->mode === self::MODE_EXTRA_LINE ) {
if ( $item_mismatch < 0 ) {
// Do floors on item amounts so item_mismatch is a positive value.
foreach ( $this->purchase_unit['items'] as $index => $item ) {
// Get a more intelligent adjustment mechanism.
$increment = ( new MoneyFormatter() )->minimum_increment( $item['unit_amount']['currency_code'] );
$this->purchase_unit['items'][ $index ]['unit_amount'] = ( new Money(
( (float) $item['unit_amount']['value'] ) - $increment,
$item['unit_amount']['currency_code']
) )->to_array();
}
}
$item_mismatch = $this->calculate_item_mismatch();
if ( $item_mismatch > 0 ) {
// Add extra line item with roundings.
$line_name = $this->extra_line_name;
$roundings_money = new Money( $item_mismatch, $this->currency_code() );
$this->purchase_unit['items'][] = ( new Item( $line_name, $roundings_money, 1 ) )->to_array();
$this->set_last_message(
__( 'Item amount mismatch. Extra line added.', 'woocommerce-paypal-payments' )
);
}
$item_mismatch = $this->calculate_item_mismatch();
}
if ( $item_mismatch !== 0.0 ) {
// Ditch items.
if ( $this->allow_ditch_items && isset( $this->purchase_unit['items'] ) ) {
unset( $this->purchase_unit['items'] );
$this->set_last_message(
__( 'Item amount mismatch. Items ditched.', 'woocommerce-paypal-payments' )
);
}
}
}
/**
* The sanitizes the purchase_unit items tax.
*
* @return void
*/
private function sanitize_item_tax_mismatch(): void {
$tax_mismatch = $this->calculate_tax_mismatch();
if ( $this->allow_ditch_items && $tax_mismatch !== 0.0 ) {
// Unset tax in items.
foreach ( $this->purchase_unit['items'] as $index => $item ) {
if ( isset( $this->purchase_unit['items'][ $index ]['tax'] ) ) {
unset( $this->purchase_unit['items'][ $index ]['tax'] );
}
if ( isset( $this->purchase_unit['items'][ $index ]['tax_rate'] ) ) {
unset( $this->purchase_unit['items'][ $index ]['tax_rate'] );
}
}
}
}
/**
* The sanitizes the purchase_unit breakdown.
*
* @return void
*/
private function sanitize_breakdown_mismatch(): void {
$breakdown_mismatch = $this->calculate_breakdown_mismatch();
if ( $this->allow_ditch_items && $breakdown_mismatch !== 0.0 ) {
// Ditch breakdowns and items.
if ( isset( $this->purchase_unit['items'] ) ) {
unset( $this->purchase_unit['items'] );
}
if ( isset( $this->purchase_unit['amount']['breakdown'] ) ) {
unset( $this->purchase_unit['amount']['breakdown'] );
}
$this->has_ditched_items_breakdown = true;
$this->set_last_message(
__( 'Breakdown mismatch. Items and breakdown ditched.', 'woocommerce-paypal-payments' )
);
}
}
/**
* The calculates amount mismatch of items sums with breakdown.
*
* @return float
*/
private function calculate_item_mismatch(): float {
$item_total = $this->breakdown_value( 'item_total' );
if ( ! $item_total ) {
return 0;
}
$remaining_item_total = array_reduce(
$this->items(),
function ( float $total, array $item ): float {
return $total - (float) $item['unit_amount']['value'] * (float) $item['quantity'];
},
$item_total
);
return round( $remaining_item_total, 2 );
}
/**
* The calculates tax mismatch of items sums with breakdown.
*
* @return float
*/
private function calculate_tax_mismatch(): float {
$tax_total = $this->breakdown_value( 'tax_total' );
$items_with_tax = array_filter(
$this->items(),
function ( array $item ): bool {
return isset( $item['tax'] );
}
);
if ( ! $tax_total || empty( $items_with_tax ) ) {
return 0;
}
$remaining_tax_total = array_reduce(
$this->items(),
function ( float $total, array $item ): float {
$tax = $item['tax'] ?? false;
if ( $tax ) {
$total -= (float) $tax['value'] * (float) $item['quantity'];
}
return $total;
},
$tax_total
);
return round( $remaining_tax_total, 2 );
}
/**
* The calculates mismatch of breakdown sums with total amount.
*
* @return float
*/
private function calculate_breakdown_mismatch(): float {
$breakdown = $this->breakdown();
if ( ! $breakdown ) {
return 0;
}
$amount_total = 0.0;
$amount_total += $this->breakdown_value( 'item_total' );
$amount_total += $this->breakdown_value( 'tax_total' );
$amount_total += $this->breakdown_value( 'shipping' );
$amount_total -= $this->breakdown_value( 'discount' );
$amount_total -= $this->breakdown_value( 'shipping_discount' );
$amount_total += $this->breakdown_value( 'handling' );
$amount_total += $this->breakdown_value( 'insurance' );
$amount_str = $this->amount()['value'] ?? 0;
$amount_total_str = ( new Money( $amount_total, $this->currency_code() ) )->value_str();
return $amount_str - $amount_total_str;
}
/**
* Indicates if the items and breakdown were ditched.
*
* @return bool
*/
public function has_ditched_items_breakdown(): bool {
return $this->has_ditched_items_breakdown;
}
/**
* Returns the last sanitization message.
*
* @return string
*/
public function get_last_message(): string {
return $this->last_message;
}
/**
* Set the last sanitization message.
*
* @param string $message The message.
*/
public function set_last_message( string $message ): void {
$this->last_message = $message;
}
}