Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ composer.lock

# Local dev config
.env.local
.frpc.pid

# Python
*.venv
87 changes: 86 additions & 1 deletion Api/Config/RepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,21 @@ interface RepositoryInterface
public const XML_PATH_ENABLE_PO_NUMBER = 'payment/two_payment/enable_po_number';
public const XML_PATH_PAYMENT_TERMS_TYPE = 'payment/two_payment/payment_terms_type';
public const XML_PATH_PAYMENT_TERMS_DURATION_DAYS = 'payment/two_payment/payment_terms_duration_days';
public const XML_PATH_PAYMENT_TERMS = 'payment/two_payment/payment_terms';
public const XML_PATH_DEFAULT_PAYMENT_TERM = 'payment/two_payment/default_payment_term';
public const XML_PATH_SURCHARGE_TYPE = 'payment/two_payment/surcharge_type';
public const XML_PATH_SURCHARGE_DIFFERENTIAL = 'payment/two_payment/surcharge_differential';
public const XML_PATH_SURCHARGE_LINE_DESCRIPTION = 'payment/two_payment/surcharge_line_description';
public const XML_PATH_SURCHARGE_TAX_RATE = 'payment/two_payment/surcharge_tax_rate';
public const XML_PATH_DEFAULT_PRODUCT_TAX_CLASS = 'tax/classes/default_product_tax_class';
public const XML_PATH_VERSION = 'payment/two_payment/version';
public const XML_PATH_DEBUG = 'payment/two_payment/debug';

/** Configurable limits — override in fork */
public const AVAILABLE_PAYMENT_TERMS = [14, 30, 60, 90];
public const SURCHARGE_FIXED_MAX = 100;
public const SURCHARGE_PERCENTAGE_MAX = 100;
Comment on lines +54 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defining data arrays like AVAILABLE_PAYMENT_TERMS, SURCHARGE_FIXED_MAX, and SURCHARGE_PERCENTAGE_MAX directly as constants in an interface can make the interface less abstract. While the comment "override in fork" suggests this is the intended mechanism for customization, a more flexible approach might be to define methods that return these values, allowing implementing classes to provide their own logic or data sources. This would keep the interface purely behavioral.


/** Weight unit */
public const XML_PATH_WEIGHT_UNIT = 'general/locale/weight_unit';

Expand Down Expand Up @@ -263,11 +275,84 @@ public function isAddressSearchEnabled(?int $storeId = null): bool;
public function getPaymentTermsType(?int $storeId = null): string;

/**
* Get payment terms duration days
* Get payment terms duration days (custom term, 0 = not set)
*
* @param int|null $storeId
*
* @return int
*/
public function getPaymentTermsDurationDays(?int $storeId = null): int;

/**
* Get selected payment terms from multiselect
*
* @param int|null $storeId
*
* @return array
*/
public function getPaymentTerms(?int $storeId = null): array;

/**
* Get all buyer-facing terms (union of multiselect + custom duration)
*
* @param int|null $storeId
*
* @return array
*/
public function getAllBuyerTerms(?int $storeId = null): array;

/**
* Get default payment term
*
* @param int|null $storeId
*
* @return int
*/
public function getDefaultPaymentTerm(?int $storeId = null): int;

/**
* Get surcharge type
*
* @param int|null $storeId
*
* @return string
*/
public function getSurchargeType(?int $storeId = null): string;

/**
* Check if differential surcharge is enabled
*
* @param int|null $storeId
*
* @return bool
*/
public function isSurchargeDifferential(?int $storeId = null): bool;

/**
* Get surcharge line item description
*
* @param int|null $storeId
*
* @return string
*/
public function getSurchargeLineDescription(?int $storeId = null): string;

/**
* Get surcharge tax rate (percentage)
*
* @param int|null $storeId
*
* @return float
*/
public function getSurchargeTaxRate(?int $storeId = null): float;

/**
* Get surcharge config for a specific term
*
* @param int $days
* @param int|null $storeId
*
* @return array{percentage: int, fixed: int, limit: float}
*/
public function getSurchargeConfig(int $days, ?int $storeId = null): array;
}
195 changes: 195 additions & 0 deletions Block/Adminhtml/System/Config/Field/SurchargeGrid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
/**
* Copyright © Two.inc All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Two\Gateway\Block\Adminhtml\System\Config\Field;

use Magento\Backend\Block\Template\Context;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Two\Gateway\Api\Config\RepositoryInterface as ConfigRepository;

/**
* Renders a grid of surcharge inputs (fixed, percentage, limit) per payment term.
*
* Replaces the individual per-term surcharge fields with a compact table.
* Reads available terms from the multiselect + custom duration config,
* and the limits from RepositoryInterface constants (fork-friendly).
*/
class SurchargeGrid extends Field
{
/** @var string */
protected $_template = 'Two_Gateway::system/config/field/surcharge-grid.phtml';

/** @var ScopeConfigInterface */
private $scopeConfig;

/** @var string */
private $scope = 'default';

/** @var int */
private $scopeId = 0;

public function __construct(
Context $context,
ScopeConfigInterface $scopeConfig,
array $data = []
) {
parent::__construct($context, $data);
$this->scopeConfig = $scopeConfig;
}

/**
* @inheritDoc
*/
public function render(AbstractElement $element): string
{
$this->resolveScope($element);
$element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
return parent::render($element);
}

/**
* @inheritDoc
*/
protected function _getElementHtml(AbstractElement $element): string
{
return $this->_toHtml();
}

/**
* Get the sorted list of active payment terms (standard + custom).
*/
public function getActiveTerms(): array
{
$selected = $this->getConfigValue(ConfigRepository::XML_PATH_PAYMENT_TERMS);
$terms = array_filter(array_map('intval', explode(',', (string)$selected)));

$custom = (int)$this->getConfigValue(ConfigRepository::XML_PATH_PAYMENT_TERMS_DURATION_DAYS);
if ($custom > 0) {
$terms[] = $custom;
}

$terms = array_unique($terms);
sort($terms);
return array_values($terms);
}

/**
* Get the saved surcharge value for a given term and field.
*/
public function getSavedValue(int $days, string $field): string
{
$path = sprintf('payment/two_payment/surcharge_%d_%s', $days, $field);
$value = $this->getConfigValue($path);
return $value !== null ? (string)$value : '';
}

/**
* Get the default payment term (for differential mode highlighting).
*/
public function getDefaultTerm(): int
{
return (int)$this->getConfigValue(ConfigRepository::XML_PATH_DEFAULT_PAYMENT_TERM);
}

/**
* Get the surcharge type (none, percentage, fixed, fixed_and_percentage).
*/
public function getSurchargeType(): string
{
return (string)$this->getConfigValue(ConfigRepository::XML_PATH_SURCHARGE_TYPE);
}

public function getMaxFixed(): int
{
return ConfigRepository::SURCHARGE_FIXED_MAX;
}

public function getMaxPercentage(): int
{
return ConfigRepository::SURCHARGE_PERCENTAGE_MAX;
}

/**
* Get the HTML field name for a surcharge input.
*
* Nests under the surcharge_grid field's value so the backend model receives it:
* groups[payment_terms][fields][surcharge_grid][value][{days}][{field}]
*/
public function getFieldName(int $days, string $field): string
{
return sprintf(
'groups[payment_terms][fields][surcharge_grid][value][%d][%s]',
$days,
$field
);
}

/**
* Get the "inherit" checkbox name for scope override.
*/
public function getInheritName(int $days, string $field): string
{
return sprintf(
'groups[payment_terms][fields][surcharge_grid][inherit][%d][%s]',
$days,
$field
);
}

/**
* Check if a field is using the inherited (default/website) value at current scope.
*/
public function isInherited(int $days, string $field): bool
{
if ($this->scope === 'default') {
return false;
}
$path = sprintf('payment/two_payment/surcharge_%d_%s', $days, $field);
// Check if a value exists at this specific scope
$value = $this->scopeConfig->getValue($path, $this->scope, $this->scopeId);
$defaultValue = $this->scopeConfig->getValue($path);
// If the scope-specific value equals the default, it's likely inherited
// (Magento doesn't expose "is this overridden" directly for system config)
return $value === $defaultValue;
}

/**
* Whether we're at a non-default scope (website or store).
*/
public function isNonDefaultScope(): bool
{
return $this->scope !== 'default';
}

/**
* Available term constants (for JS to know which terms are standard).
*/
public function getAvailablePaymentTerms(): array
{
return ConfigRepository::AVAILABLE_PAYMENT_TERMS;
}

private function resolveScope(AbstractElement $element): void
{
$form = $element->getForm();
if ($form) {
$scope = (string)$form->getScope();
$this->scope = ($scope !== '') ? $scope : 'default';
$this->scopeId = (int)$form->getScopeId();
}
}

private function getConfigValue(string $path)
{
if ($this->scope !== 'default') {
return $this->scopeConfig->getValue($path, $this->scope, $this->scopeId);
}
return $this->scopeConfig->getValue($path);
}
}
63 changes: 63 additions & 0 deletions Block/Adminhtml/System/Config/Field/SurchargeTaxRate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* Copyright © Two.inc All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Two\Gateway\Block\Adminhtml\System\Config\Field;

use Magento\Backend\Block\Template\Context;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Two\Gateway\Api\Config\RepositoryInterface as ConfigRepository;

/**
* Surcharge Tax Rate field with dynamic comment.
*
* Shows the store's default tax rate when available, or a red warning
* when no tax rules are configured.
*/
class SurchargeTaxRate extends Field
{
/**
* @var ConfigRepository
*/
private $configRepository;

public function __construct(
Context $context,
ConfigRepository $configRepository,
array $data = []
) {
parent::__construct($context, $data);
$this->configRepository = $configRepository;
}

/**
* @inheritDoc
*/
protected function _getElementHtml(AbstractElement $element): string
{
$defaultRate = $this->configRepository->getDefaultTaxRate();

if ($defaultRate > 0) {
$element->setComment(
(string)__(
'Leave empty to use your store\'s default tax rate (%1%%). Enter 0 for tax-exempt.',
number_format($defaultRate, 1)
)
);
} else {
$element->setComment(
'<span style="color: #e22626; font-weight: bold;">'
. (string)__('Warning: No tax rules are configured for your store. '
. 'Configure tax rules in Stores → Tax Rules to ensure correct surcharge tax calculation. '
. 'Enter a rate manually, or enter 0 for tax-exempt.')
. '</span>'
);
}

return parent::_getElementHtml($element);
}
}
Loading
Loading