diff --git a/Model/Config/Source/CaptureTrigger.php b/Model/Config/Source/CaptureTrigger.php new file mode 100644 index 0000000..ce02eff --- /dev/null +++ b/Model/Config/Source/CaptureTrigger.php @@ -0,0 +1,39 @@ + + * @copyright 2021 The Federal Tax Authority, LLC d/b/a TaxCloud + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +namespace Taxcloud\Magento2\Model\Config\Source; + +use \Magento\Framework\Data\OptionSourceInterface; + +class CaptureTrigger implements OptionSourceInterface +{ + const ORDER_CREATION = 'order_creation'; + const PAYMENT = 'payment'; + const SHIPMENT = 'shipment'; + + /** + * @return array + */ + public function toOptionArray() + { + return [ + ['value' => self::ORDER_CREATION, 'label' => __('On order creation')], + ['value' => self::PAYMENT, 'label' => __('On payment')], + ['value' => self::SHIPMENT, 'label' => __('On shipment')], + ]; + } +} diff --git a/Observer/Sales/Complete.php b/Observer/Sales/Complete.php index 29e35e6..2f0aafd 100644 --- a/Observer/Sales/Complete.php +++ b/Observer/Sales/Complete.php @@ -19,9 +19,18 @@ use \Magento\Framework\Event\ObserverInterface; use \Magento\Framework\Event\Observer; +use Taxcloud\Magento2\Model\Config\Source\CaptureTrigger; class Complete implements ObserverInterface { + /** @var string Magento event: order placed */ + const EVENT_ORDER_PLACE_AFTER = 'sales_order_place_after'; + + /** @var string Magento event: invoice paid */ + const EVENT_INVOICE_PAY = 'sales_order_invoice_pay'; + + /** @var string Magento event: shipment saved */ + const EVENT_SHIPMENT_SAVE_AFTER = 'sales_order_shipment_save_after'; /** * Core store config @@ -69,6 +78,17 @@ public function info() } /** + * Event names that correspond to each capture trigger option. + */ + private static $triggerToEvent = [ + CaptureTrigger::ORDER_CREATION => self::EVENT_ORDER_PLACE_AFTER, + CaptureTrigger::PAYMENT => self::EVENT_INVOICE_PAY, + CaptureTrigger::SHIPMENT => self::EVENT_SHIPMENT_SAVE_AFTER, + ]; + + /** + * Run only when the current event matches the configured "Capture in TaxCloud" setting. + * * @param Observer $observer */ public function execute( @@ -81,10 +101,64 @@ public function execute( return; } - $this->tclogger->info('Running Observer sales_order_place_after'); + $eventName = $observer->getEvent()->getName(); + $configuredTrigger = $this->scopeConfig->getValue( + 'tax/taxcloud_settings/capture_trigger', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($configuredTrigger === null || $configuredTrigger === '') { + $configuredTrigger = CaptureTrigger::ORDER_CREATION; + } + + $expectedEvent = isset(self::$triggerToEvent[$configuredTrigger]) + ? self::$triggerToEvent[$configuredTrigger] + : self::$triggerToEvent[CaptureTrigger::ORDER_CREATION]; + + if ($eventName !== $expectedEvent) { + return; + } + + $this->tclogger->info('Running Observer ' . $eventName . ' (capture trigger: ' . $configuredTrigger . ')'); - $order = $observer->getEvent()->getOrder(); + $order = $this->getOrderFromObserver($observer, $eventName); + if (!$order) { + return; + } $this->tcapi->authorizeCapture($order); } + + /** + * Get order from observer based on event. Returns null if we should skip (e.g. not first invoice/shipment). + * + * @param Observer $observer + * @param string $eventName + * @return \Magento\Sales\Model\Order|null + */ + private function getOrderFromObserver(Observer $observer, $eventName) + { + $event = $observer->getEvent(); + + if ($eventName === self::EVENT_ORDER_PLACE_AFTER) { + return $event->getOrder(); + } + + if ($eventName === self::EVENT_INVOICE_PAY) { + $order = $event->getInvoice()->getOrder(); + if ($order->getInvoiceCollection()->getSize() > 1) { + return null; + } + return $order; + } + + if ($eventName === self::EVENT_SHIPMENT_SAVE_AFTER) { + $order = $event->getShipment()->getOrder(); + if ($order->getShipmentCollection()->getSize() > 1) { + return null; + } + return $order; + } + + return null; + } } diff --git a/README.md b/README.md index bdb398e..4608400 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ Navigate to *Stores → Configuration* and then *Sales → Tax*. * **Default TIC** - Enter the Taxability Information Code you would like to use for products where an explicit TIC has not been specified. * **Shipping TIC** - Enter the Taxability Information Code you would like to use for shipping costs. Use `11010` if you charge only postage, and `11000` for shipping & handling. * **Cache Lifetime** - Enter the amount of time in seconds you would like to cache the sales tax lookup and verify address API calls. The default value is `86400` (24 hours), or enter `0` to disable caching for development purposes. +* **Capture in TaxCloud** - Choose when the order is sent to TaxCloud: *On order creation* (at checkout; default), *On payment* (when an invoice is paid; recommended to avoid canceled orders reaching TaxCloud), or *On shipment* (when a shipment is created). For online payment methods, "on creation" and "on payment" often fire together; the choice matters for offline payment or when you only want to report tax on fulfilled orders. #### Product Settings @@ -243,8 +244,8 @@ Each of these situations can be accomplished using an event observer. For every | `taxcloud_lookup_after` | Emitted after the `Lookup` call to get tax rates | `$result`, `$customer`, `$address`, `$quote`, `$itemsByType`, `$shippingAssignment` | | `taxcloud_verify_address_before` | Emitted before the `VerifyAddress` call during checkout | `$params` | | `taxcloud_verify_address_after` | Emitted after the `VerifyAddress` call during checkout | `$result` | -| `taxcloud_authorized_with_capture_before` | Emitted before the `AuthorizedWithCapture` call when an order is placed | `$params`, `$order` | -| `taxcloud_authorized_with_capture_after` | Emitted after the `AuthorizedWithCapture` call when an order is placed | `$result`, `$order` | +| `taxcloud_authorized_with_capture_before` | Emitted before the `AuthorizedWithCapture` call (when the order is sent per "Capture in TaxCloud" setting) | `$params`, `$order` | +| `taxcloud_authorized_with_capture_after` | Emitted after the `AuthorizedWithCapture` call (when the order is sent per "Capture in TaxCloud" setting) | `$result`, `$order` | | `taxcloud_returned_before` | Emitted before the `Returned` call when a credit memo is created or when a canceled unpaid order is reversed | `$params`, `$order`, `$items`, `$creditmemo` | | `taxcloud_returned_after` | Emitted after the `Returned` call when a credit memo is created or when a canceled unpaid order is reversed | `$result`, `$order`, `$items`, `$creditmemo` | diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index c6c1710..bc935e4 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -139,6 +139,16 @@ + + + Taxcloud\Magento2\Model\Config\Source\CaptureTrigger + tax/taxcloud_settings/capture_trigger + On order creation: at checkout (default; canceled orders may reach TaxCloud). On payment: when invoice is paid (recommended to avoid canceled orders). On shipment: when shipment is created. + + 1 + + + diff --git a/etc/config.xml b/etc/config.xml index 7c05a46..73e36ed 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -28,6 +28,7 @@ 11010 86400 0 + order_creation diff --git a/etc/events.xml b/etc/events.xml index 978e28a..5509dc2 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -21,6 +21,12 @@ + + + + + +