Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.8 #3865

Merged
merged 39 commits into from
Jan 30, 2025
Merged

4.8 #3865

Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
53ab687
add changelog WIP
nfourtythree Jan 13, 2025
abee563
Update CI
nfourtythree Jan 13, 2025
9e71fd9
Merge branch '4.x' into 4.8
nfourtythree Jan 13, 2025
697cfe4
Show archived gateways table
nfourtythree Jan 14, 2025
27877db
Changelog
nfourtythree Jan 14, 2025
2c676e3
WIP
lukeholder Jan 22, 2025
ff57179
Merge branch '4.x' into feature/4.8-tax-id-validators
lukeholder Jan 22, 2025
0420bc5
reenabled cache
lukeholder Jan 22, 2025
703e77c
Update address validator
lukeholder Jan 22, 2025
57ec5d3
Remove uneeded
lukeholder Jan 22, 2025
dab6281
Remove uneeded
lukeholder Jan 22, 2025
c74daf1
fix comments
lukeholder Jan 22, 2025
7c0f2a5
validator should be format first
lukeholder Jan 22, 2025
0bc175f
code comments
lukeholder Jan 22, 2025
918c99c
use non deprecated method
lukeholder Jan 22, 2025
e1ea047
Cleanup
lukeholder Jan 22, 2025
bab522b
cleanup
lukeholder Jan 22, 2025
a4b93f7
Missing translation
lukeholder Jan 22, 2025
3c4f28f
Not needed
lukeholder Jan 22, 2025
7b72043
fixes
lukeholder Jan 22, 2025
1e464b8
Cleanup
lukeholder Jan 22, 2025
259e662
Merge branch '4.x' into 4.8
nfourtythree Jan 22, 2025
d3b05d6
Merge branch '4.8' into feature/4.8-tax-id-validators
lukeholder Jan 22, 2025
b542f43
Merge pull request #3847 from craftcms/feature/pt-2373-4x-getgatewayb…
nfourtythree Jan 22, 2025
96f0d56
Add release notes
lukeholder Jan 22, 2025
0673e0b
Merge branch '4.8' into feature/4.8-tax-id-validators
lukeholder Jan 22, 2025
9e672d9
Cleanup
lukeholder Jan 22, 2025
a6d080b
Fix tests
lukeholder Jan 22, 2025
2a6092d
Merge pull request #3857 from craftcms/feature/4.8-tax-id-validators
lukeholder Jan 22, 2025
b0d6a14
Translation cleanup
brandonkelly Jan 22, 2025
e2418f0
Don't use the 'commerce' t9n category in example templates
brandonkelly Jan 22, 2025
89d1cc2
notes field should be text on upgrade
lukeholder Jan 28, 2025
0d19235
Cleaup
lukeholder Jan 28, 2025
334d748
Merge branch '4.x' into 4.8
lukeholder Jan 28, 2025
56b3060
Changelog tweaks
brandonkelly Jan 29, 2025
c133fea
Merge branch '4.x' into 4.8
nfourtythree Jan 29, 2025
842172a
Changelog item
nfourtythree Jan 29, 2025
60f1c7f
Aria labels for future, tidy up nav/breadcrumb consistency
nfourtythree Jan 30, 2025
bd5b63d
Prep for 4.8.0
brandonkelly Jan 30, 2025
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
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
lukeholder committed Jan 22, 2025
commit 2c676e39c1c005b1f44890b31694563d34e1f82f
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

## Unreleased

- Improved the performance of recalculating shipping method prices. ([#3841](https://github.com/craftcms/commerce/pull/3841))
- Fixed a bug where Edit Product pages would allow duplication for users that didn’t have permission to duplicate the product. ([#3819](https://github.com/craftcms/commerce/issues/3819))

## 4.7.2 - 2024-12-18
1,015 changes: 510 additions & 505 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
@@ -80,6 +80,7 @@
use craft\commerce\services\TaxZones;
use craft\commerce\services\Transactions;
use craft\commerce\services\Variants as VariantsService;
use craft\commerce\services\Vat;
use craft\commerce\services\Webhooks;
use craft\commerce\web\twig\CraftVariableBehavior;
use craft\commerce\web\twig\Extension;
@@ -195,6 +196,7 @@ public static function config(): array
'transactions' => ['class' => Transactions::class],
'customers' => ['class' => Customers::class],
'variants' => ['class' => VariantsService::class],
'vat' => ['class' => Vat::class],
'webhooks' => ['class' => Webhooks::class],
],
];
@@ -210,7 +212,7 @@ public static function editions(): array
/**
* @inheritDoc
*/
public string $schemaVersion = '4.7.0.1';
public string $schemaVersion = '4.8.0.0';

/**
* @inheritdoc
43 changes: 32 additions & 11 deletions src/adjusters/Tax.php
Original file line number Diff line number Diff line change
@@ -10,13 +10,16 @@
use Craft;
use craft\base\Component;
use craft\commerce\base\AdjusterInterface;
use craft\commerce\base\TaxIdValidatorInterface;
use craft\commerce\elements\Order;
use craft\commerce\helpers\Currency;
use craft\commerce\models\OrderAdjustment;
use craft\commerce\models\TaxAddressZone;
use craft\commerce\models\TaxRate;
use craft\commerce\Plugin;
use craft\commerce\records\TaxRate as TaxRateRecord;
use craft\commerce\services\Taxes;
use craft\commerce\taxidvalidators\EuVatIdValidator;
use craft\elements\Address;
use DvK\Vat\Validator;
use Exception;
@@ -118,17 +121,17 @@ private function _adjustInternal(): array
private function _getAdjustments(TaxRate $taxRate): array
{
$adjustments = [];
$hasValidEuVatId = false;
$hasValidTaxId = false;

$zoneMatches = $taxRate->getIsEverywhere() || ($taxRate->getTaxZone() && $this->_matchAddress($taxRate->getTaxZone()));

if ($zoneMatches && $taxRate->isVat) {
$hasValidEuVatId = $this->organizationTaxId();
if ($zoneMatches && $taxRate->hasTaxIdValidators()) {
$hasValidTaxId = $this->organizationTaxIdIsValidTaxId($taxRate->getSelectedEnabledTaxIdValidators());
}

$removeIncluded = (!$zoneMatches && $taxRate->removeIncluded);
$removeDueToVat = ($zoneMatches && $hasValidEuVatId && $taxRate->removeVatIncluded);
if ($removeIncluded || $removeDueToVat) {
$removeDueToVatId = ($zoneMatches && $hasValidTaxId && $taxRate->removeVatIncluded);
if ($removeIncluded || $removeDueToVatId) {

// Is this an order level tax rate?
if (in_array($taxRate->taxable, TaxRateRecord::ORDER_TAXABALES, false)) {
@@ -195,7 +198,7 @@ private function _getAdjustments(TaxRate $taxRate): array
return $adjustments;
}

if (!$zoneMatches || ($taxRate->isVat && $hasValidEuVatId)) {
if (!$zoneMatches || ($taxRate->hasTaxIdValidators() && $hasValidTaxId)) {
return [];
}

@@ -323,7 +326,7 @@ private function _matchAddress(TaxAddressZone $zone): bool
/**
* @return bool
*/
private function organizationTaxId(): bool
private function organizationTaxIdIsValidTaxId(array $validators): bool
{
if (!$this->_address) {
return false;
@@ -336,11 +339,11 @@ private function organizationTaxId(): bool
return false;
}

$validOrganizationTaxId = Craft::$app->getCache()->exists('commerce:validVatId:' . $this->_address->organizationTaxId);
$validOrganizationTaxId = false;//Craft::$app->getCache()->exists('commerce:validVatId:' . $this->_address->organizationTaxId);

// If we do not have a valid VAT ID in cache, see if we can get one from the API
if (!$validOrganizationTaxId) {
$validOrganizationTaxId = $this->validateVatNumber($this->_address->organizationTaxId);
$validOrganizationTaxId = $this->validateTaxIdNumber($this->_address->organizationTaxId, $validators);
}

if ($validOrganizationTaxId) {
@@ -355,19 +358,37 @@ private function organizationTaxId(): bool
/**
* @param string $businessVatId
* @return bool
* @deprecated in 4.8.0. Use `validateEuVatNumber()` instead, passing the validators you want to check the ID with.
*/
protected function validateVatNumber(string $businessVatId): bool
{
$oldValidator = [new EuVatIdValidator()];
return $this->validateTaxIdNumber($businessVatId, $oldValidator);
}

/**
* @param string $organizationTaxId
* @param TaxIdValidatorInterface[] $validators
* @return bool
*/
protected function validateTaxIdNumber(string $organizationTaxId, array $validators = []): bool
{
try {
return $this->_getVatValidator()->validate($businessVatId);
foreach ($validators as $validator) {
if ($validator->validate($organizationTaxId)) {
return true;
}
}
} catch (Exception $e) {
Craft::error('Communication with VAT API failed: ' . $e->getMessage(), __METHOD__);

return false;
}

return false;
}

private function _getVatValidator(): Validator
private function _getEuVatValidator(): Validator
{
if ($this->_vatValidator === null) {
$this->_vatValidator = new Validator();
45 changes: 45 additions & 0 deletions src/base/TaxIdValidatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace craft\commerce\base;

interface TaxIdValidatorInterface
{
/**
* The display name of this tax ID type.
*
* @return string
*/
public static function displayName(): string;

/**
* Tests if the ID looks generally correct. This would usually be something like a regex check.
*
* @return bool
* @param string $idNumber
*/
public function validateFormat(string $idNumber): bool;

/**
* Tests if the ID exists as valid in the country's tax system. This would usually be an API call.
*
* @param string $idNumber
* @return bool
*/
public function validateExistence(string $idNumber): bool;

/**
* This would usually just call validateFormat() and then validateExistence() and return the result.
*
* @param string $idNumber
* @return bool
*/
public function validate(string $idNumber): bool;

/**
* Tests if the validator is available for use by tax rates.
* This would usually be a check against the existence or settings or API keys so that the validator can be used.
*
* @return bool
*/
public static function isEnabled(): bool;
}
12 changes: 10 additions & 2 deletions src/behaviors/ValidateOrganizationTaxIdBehavior.php
Original file line number Diff line number Diff line change
@@ -89,9 +89,17 @@ public function validateOrganizationTaxId(): void
private function _validateVatNumber(string $businessVatId): bool
{
try {
return $this->_getVatValidator()->validate($businessVatId);
$validators = Plugin::getInstance()->getTaxes()->getEngine()->getValidators();
$valid = false;
foreach ($validators as $validator) {
if ($validator->validateExistence($businessVatId)) {
$valid = true;
break;
}
}
return $valid;
} catch (Exception $e) {
Craft::error('Communication with VAT API failed: ' . $e->getMessage(), __METHOD__);
Craft::error('Communication with Tax ID validators failed: ' . $e->getMessage(), __METHOD__);

return false;
}
12 changes: 11 additions & 1 deletion src/controllers/TaxRatesController.php
Original file line number Diff line number Diff line change
@@ -156,6 +156,13 @@ public function actionEdit(int $id = null, TaxRate $taxRate = null): Response
);
$variables['newTaxCategoryJs'] = $view->clearJsBuffer(false);

$taxIdValidators = Plugin::getInstance()->getTaxes()->getTaxIdValidators();
foreach ($taxIdValidators as $validator) {
if ($validator::isEnabled()) {
$variables['taxIdValidators'][] = $validator;
}
}

return $this->renderTemplate('commerce/tax/taxrates/_edit', $variables);
}

@@ -181,12 +188,15 @@ public function actionSave(): void
$taxRate->include = (bool)$this->request->getBodyParam('include');
$taxRate->removeIncluded = (bool)$this->request->getBodyParam('removeIncluded');
$taxRate->removeVatIncluded = (bool)$this->request->getBodyParam('removeVatIncluded');
$taxRate->isVat = (bool)$this->request->getBodyParam('isVat');
$taxRate->taxable = $this->request->getBodyParam('taxable');
$taxRate->taxCategoryId = (int)$this->request->getBodyParam('taxCategoryId') ?: null;
$taxRate->taxZoneId = (int)$this->request->getBodyParam('taxZoneId') ?: null;
$taxRate->rate = Localization::normalizePercentage($this->request->getBodyParam('rate'));

// data comes in as className => bool, we want just the class names that are true
$validators = collect($this->request->getBodyParam('taxIdValidators'))->filter(fn($enabled) => (bool)$enabled)->keys();
$taxRate->taxIdValidators = $validators->toArray();

// Save it
if (Plugin::getInstance()->getTaxRates()->saveTaxRate($taxRate)) {
$this->setSuccessFlash(Craft::t('commerce', 'Tax rate saved.'));
25 changes: 25 additions & 0 deletions src/events/TaxIdValidatorsEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\commerce\events;

use craft\commerce\base\TaxIdValidatorInterface;
use yii\base\Event;

/**
* Class TaxIdValidatorsEvent
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 4.8.0
*/
class TaxIdValidatorsEvent extends Event
{
/**
* @var TaxIdValidatorInterface[] Holds the registered tax ID validators.
*/
public array $validators = [];
}
3 changes: 2 additions & 1 deletion src/migrations/Install.php
Original file line number Diff line number Diff line change
@@ -741,7 +741,8 @@ public function createTables(): void
'code' => $this->string(),
'rate' => $this->decimal(14, 10)->notNull(),
'include' => $this->boolean()->notNull()->defaultValue(false),
'isVat' => $this->boolean()->notNull()->defaultValue(false), // TODO rename to isEuVat #COM-45
'isVat' => $this->boolean()->notNull()->defaultValue(false), // Remove in Commerce 6
'taxIdValidators' => $this->text(),
'removeIncluded' => $this->boolean()->notNull()->defaultValue(false),
'removeVatIncluded' => $this->boolean()->notNull()->defaultValue(false),
'taxable' => $this->enum('taxable', ['purchasable', 'price', 'shipping', 'price_shipping', 'order_total_shipping', 'order_total_price'])->notNull(),
40 changes: 40 additions & 0 deletions src/migrations/m250120_080035_move_to_tax_id_validators.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace craft\commerce\migrations;

use craft\db\Migration;

/**
* m250120_080035_move_to_tax_id_validators migration.
*/
class m250120_080035_move_to_tax_id_validators extends Migration
{
/**
* @inheritdoc
*/
public function safeUp(): bool
{
$this->addColumn('{{%commerce_taxrates}}', 'taxIdValidators', $this->text()->after('isVat'));

$taxRates = (new \craft\db\Query())
->select(['id', 'isVat'])
->from(['{{%commerce_taxrates}}'])
->all();

foreach ($taxRates as $taxRate) {
$taxIdValidators = $taxRate['isVat'] ? ['craft\commerce\taxidvalidators\EuVatIdValidator'] : [];
$this->update('{{%commerce_taxrates}}', ['taxIdValidators' => json_encode($taxIdValidators)], ['id' => $taxRate['id']]);
}

return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo "m250120_080035_move_to_tax_id_validators cannot be reverted.\n";
return false;
}
}
Loading