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 @@
11010864000
+ 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 @@
+
+
+
+
+
+