How can I integrate the Stripe Live Tax API in Shopware 6?
Live tax rate calculation is required for e-commerce websites. To ensure legal compliance with dynamic tax rates, enhance customer experience through accurate cost transparency, and automate processes for efficient global operations.
This feature guarantees real-time, error-free tax calculations, facilitating seamless transactions and precise financial reporting.
Steps for live tax calculation using Stripe Live Tax API
- Collect API key from Stripe developer account.
- Login to the Stripe dashboard
- Select the “Developer” menu in the header
- Select “API Keys” tab and create a Key
- Create a new subscriber with the event CartChangedEventSubscriber
Register the Subscriber in the services file
/custom/plugins/HatslogicLiveTax/src/Resources/config/services.xml
1 2 3 4 5 | <service id="HatslogicLiveTaxSubscriberCartChangedEventSubscriber"> <argument type="service" id="request_stack"/> <argument type="service" id="HatslogicLiveTaxServiceCustomTax"/> <tag name="kernel.event_subscriber"/> </service> |
We need to do the same for the following subscribers also
- CustomerAddressWrittenSubscriber
- CustomerSetDefaultShippingAddressEventSubscriber
- CustomerLoginEventSubscriber
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <service id="HatslogicLiveTaxSubscriberCustomerAddressWrittenSubscriber"> <argument type="service" id="customer_address.repository"/> <argument type="service" id="request_stack"/> <argument type="service" id="DoctrineDBALConnection"/> <tag name="kernel.event_subscriber"/> </service> <service id="HatslogicLiveTaxSubscriberCustomerSetDefaultShippingAddressEventSubscriber"> <argument type="service" id="request_stack"/> <argument type="service" id="HatslogicLiveTaxServiceCustomTax"/> <argument type="service" id="ShopwareCoreCheckoutCartSalesChannelCartService"/> <argument type="service" id="customer_address.repository"/> <tag name="kernel.event_subscriber"/><br> </service> <service id="HatslogicLiveTaxSubscriberCustomerLoginEventSubscriber"> <argument type="service" id="request_stack"/> <argument type="service" id="HatslogicLiveTaxServiceCustomTax"/> <argument type="service" id="ShopwareCoreCheckoutCartSalesChannelCartService"/> <tag name="kernel.event_subscriber"/> </service> |
/custom/plugins/HatslogicLiveTax/src/Subscriber/CartChangedEventSubscriber.php
- 1234567891011121314151617181920212223242526272829303132333435363738php declare(strict_types=1);namespace HatslogicSubscriber;use HatslogicServiceCustomTax;use SymfonyComponentEventDispatcherEventSubscriberInterface;use ShopwareCoreCheckoutCartEventCartChangedEvent;use SymfonyComponentHttpFoundationRequestStack;class CartChangedEventSubscriber implements EventSubscriberInterface{private $requestStack;private $customTax;public function __construct(RequestStack $requestStack,CustomTax $customTax){$this->requestStack = $requestStack;$this->customTax = $customTax;}public static function getSubscribedEvents(){return [CartChangedEvent::class => 'onEvent'];}public function onEvent(CartChangedEvent $event){$salesChannelContext = $event->getContext();$context = $salesChannelContext->getContext();$cart = $event->getCart();$customer = $salesChannelContext->getCustomer();$shippingLocation = $salesChannelContext->getShippingLocation();if($customer and $cart->getLineItems()->count()){$products = $cart->getLineItems();$taxData = $this->customTax->getLineitemTax( $products, $shippingLocation->getAddress(), $context );$this->requestStack->getSession()->set('HatslogicTaxData', $taxData);}}}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
1 2 3 4 5 6 7 | <service id="HatslogicLiveTaxServiceCustomTax"> <argument type="service" id="ShopwareCoreSystemSystemConfigSystemConfigService"/> <argument type="service" id="ShopwareCoreSystemSalesChannelContextSalesChannelContextService"/> <argument type="service" id="order.repository"/> <argument type="service" id="product.repository"/> <argument type="service" id="request_stack"/> </service> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | php namespace HatslogicLiveTaxService; use GuzzleHttpClient as HttpClient; use ShopwareCoreCheckoutCartCart; use ShopwareCoreFrameworkContext; use ShopwareCoreFrameworkDataAbstractionLayerSearchCriteria; use ShopwareCoreFrameworkDataAbstractionLayerSearchSortingFieldSorting; use ShopwareCoreSystemSalesChannelSalesChannelContext; use ShopwareCoreSystemSystemConfigSystemConfigService; use ShopwareCoreSystemSalesChannelContextSalesChannelContextServiceInterface; use SymfonyComponentHttpFoundationRequestStack; class CustomTax { private $httpClient; private $orderRepository; private $productRepository; private $systemConfigService; private $contextService; private $requestStack; public function __construct( SystemConfigService $systemConfigService, SalesChannelContextServiceInterface $contextService, $orderRepository, $productRepository, RequestStack $requestStack ) { $this->httpClient = new HttpClient(); $this->systemConfigService = $systemConfigService; $this->contextService = $contextService; $this->orderRepository = $orderRepository; $this->productRepository = $productRepository; $this->requestStack = $requestStack; } private function getLastOrder() { $criteria = new Criteria(); $criteria->setLimit(1); $criteria->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING)); $order = $this->orderRepository->search($criteria, Context::createDefaultContext())->first(); return $order; } public function getLineItemTax($lineItems, $shippingLocation, $context) { $zipCodeExplode = explode('-', $shippingLocation->getZipcode()); $zipCode = $zipCodeExplode[0] ?? ''; $stripeSecretKey = $this->systemConfigService->get('HatslogicLiveTax.config.hatsStripeSecretKey'); $url = 'https://api.stripe.com/v1/tax/calculations'; $shipToState = $shippingLocation->getCountryState(); $body = [ 'customer_details'=> [ 'address' => [ 'line1' => (string) $shippingLocation->getStreet(), 'city' => (string) $shippingLocation->getCity(), 'state' => (string) $shipToState, 'postal_code' => (string) $zipCode, 'country' => 'US', ] ], "address_source" => 'shipping' ]; $i = 1; foreach ($lineItems as $lineItem) { if ($lineItem->getType() == 'HatslogicLiveTax_tax') { continue; } $product = $this->getProductById($lineItem->getId(), $context); if(!$product) continue; $quantity = $lineItem->getQuantity(); $netPrice = $product->getPrice()->first()->getGross(); $totalNet = $netPrice * $quantity; $body['line_items'][] = [ 'amount' => (int) $totalNet, 'reference'=> 'L'.$i ]; $i++; $body['expand'][] = 'line_items.data.tax_breakdown'; } $body['currency'] = 'usd'; $response = $this->httpClient->post($url, [ 'form_params' => $body, 'headers' => [ "Authorization" => "Bearer " . $stripeSecretKey, "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ]); $body = json_decode($response->getBody()->getContents(), true); return $body; } } |
We are fetching this value like this
$stripeSecretKey = $this->systemConfigService->get(‘HatslogicLiveTax.config.hatsStripeSecretKey’);
- By calling the function getLineitemTax() we will get the latest tax amount by the cart.
Now we need to alter the tax calculations by this value.
For that, we need to use the cart processor and override the existing cart tax calculations
Register the cart processor in the services file
/custom/plugins/HatslogicLiveTax/src/Resources/config/services.xml
1 2 3 4 5 6 7 | <service id="HatslogicLiveTaxCoreCheckoutCustomCartProcessor"> <argument type="service" id="HatslogicLiveTaxCoreCheckoutCartPriceAbsolutePriceCalculator"/> <argument type="service" id="request_stack"/> <argument type="service" id="HatslogicLiveTaxServiceCustomTax"/> <argument type="service" id="logger"/> <tag name="shopware.cart.processor" priority="5000"/> </service> |
Create CustomCartProcessor file in the following path
/custom/plugins/HatslogicLiveTax/src/Core/Checkout/CustomCartProcessor.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | php declare(strict_types=1); namespace HatslogicLiveTaxCoreCheckout; use HatslogicLiveTaxServiceCustomTax; use ShopwareCoreCheckoutCartCart; use ShopwareCoreCheckoutCartCartBehavior; use ShopwareCoreCheckoutCartCartProcessorInterface; use ShopwareCoreCheckoutCartLineItemCartDataCollection; use ShopwareCoreCheckoutCartLineItemLineItem; use ShopwareCoreCheckoutCartLineItemLineItemCollection; use HatslogicLiveTaxCoreCheckoutCartPriceAbsolutePriceCalculator; use ShopwareCoreCheckoutCartPriceStructAbsolutePriceDefinition; use ShopwareCoreCheckoutCartRuleLineItemRule; use ShopwareCoreFrameworkStructArrayStruct; use ShopwareCoreFrameworkUuidUuid; use ShopwareCoreSystemSalesChannelSalesChannelContext; use SymfonyComponentHttpFoundationRequestStack; use PsrLogLoggerInterface; use ShopwareCoreCheckoutCartTaxStructCalculatedTaxCollection; use ShopwareCoreCheckoutCartTaxStructCalculatedTax; class CustomCartProcessor implements CartProcessorInterface { private $calculator; private $requestStack; private $customTax; private LoggerInterface $logger; public function __construct(AbsolutePriceCalculator $calculator, $requestStack, $customTax, $logger ) { $this->calculator = $calculator; $this->requestStack = $requestStack; $this->customTax = $customTax; $this->logger = $logger; } public function process(CartDataCollection $data, Cart $original, Cart $toCalculate, SalesChannelContext $context, CartBehavior $behavior): void { $customer = $context->getCustomer(); if(!$customer) { $this->requestStack->getSession()->remove('HatslogicTaxData'); return; } $products = $toCalculate->getLineItems(); if ($products->count() === 0) { return; } $taxData = $this->requestStack->getSession()->get('HatslogicTaxData'); if(!$taxData) { return; } if( isset( $taxData['error comments'] ) ) { $this->logger->info($taxData['error comments']); $this->requestStack->getSession()->set('Hatslogic_has_error', true); return; } else { $this->requestStack->getSession()->remove('Hatslogic_has_error'); } $taxTotal = 0; $taxesDetails = []; $taxesLineTotal = []; foreach ($taxData['line_items']['data'] as $taxDatum){ foreach( $taxDatum['tax_breakdown'] as $taxBreakDowns) { if($taxBreakDowns['amount'] > 0) { $slug = strtolower(str_replace(' ','_',$taxBreakDowns['tax_rate_details']['display_name'])); if(isset($taxesLineTotal["total_".$slug])) { $taxesLineTotal["total_".$slug] += $taxBreakDowns['amount']; } else { $taxesLineTotal["total_".$slug] = $taxBreakDowns['amount']; } $taxTotal +=$taxBreakDowns['tax_rate_details']['percentage_decimal']; $taxesDetails["tax_".$slug] = array( 'rate'=>$taxBreakDowns['tax_rate_details']['percentage_decimal'], 'amount'=>$taxesLineTotal["total_".$slug], 'label'=>$taxBreakDowns['tax_rate_details']['display_name'] ); } } } $body['data'] = $taxData; $body['HatslogicTax'] = $taxesDetails; if($taxTotal) { $uuid = Uuid::randomHex(); $taxLineItem = $this->createLineItem($uuid); $definition = new AbsolutePriceDefinition( $taxTotal, new LineItemRule(LineItemRule::OPERATOR_EQ, $products->getKeys()) ); $taxLineItem->setPriceDefinition($definition); $taxLineItem->setPrice( $this->calculator->calculate($definition->getPrice(), $products->getPrices(), $context) ); $taxLineItem->setPayload($body); $toCalculate->add($taxLineItem); } } private function createLineItem(string $id): LineItem { $taxLineItem = new LineItem($id, 'Hatslogic_tax', null, 1); $taxLineItem->setLabel('Hatslogic Tax'); $taxLineItem->setGood(false); $taxLineItem->setStackable(false); $taxLineItem->setRemovable(false); return $taxLineItem; } } |
Here we are updating tax calculations. If the cart has any additional kind of taxes that also will add as a separate tax item.
We need to make changes to the invoice file and the order summary pages.
For the storefront changes, we need to upgrade the following twig templates.
/custom/plugins/HatslogicLiveTax/src/Resources/views/storefront/page/checkout/checkout-item.html.twig
1 2 3 4 5 6 | {% sw_extends '@Storefront/storefront/page/checkout/checkout-item.html.twig' %} {% block page_checkout_item_container %} {% if lineItem.type !== 'Hatslogic_tax' %} {{ parent() }} {% endif %} {% endblock %} |
/custom/plugins/HatslogicLiveTax/src/Resources/views/storefront/page/checkout/summary/summary-position.html.twig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | {% sw_extends '@Storefront/storefront/page/checkout/summary/summary-position.html.twig' %} {% block page_checkout_summary_position_value %} {% set lineItem = page.order.lineItems.filterByType('Hatslogic_tax')%} {% set cartData = true %} {% if lineItem %} {% set cartData = false %} {% else %} {% set lineItem = page.cart.lineItems.filterFlatByType('Hatslogic_tax') %} {% endif %} {% if lineItem %} {% if cartData %} {% set lineItem = lineItem[0] %} {% else %} {% set lineItem = lineItem.first() %} {% endif %} {% set hatslogicLiveTax = lineItem.getPayload() %} {% set taxTotal = 0 %} {% for tax in hatslogicLiveTax.HatslogicTax %} {% if (tax.rate > 0) %} {% set taxTotal = (taxTotal + tax.amount) %} {% endif %} {% endfor %} <dd class="col-5 checkout-aside-summary-value"> {{ (summary.price.positionPrice - taxTotal)|currency }}{{ "general.star"|trans|sw_sanitize }} </dd> {% else %} {{ parent() }} {% endif %} {% endblock %} |
These are the simple required steps to set up a Stripe live tax calculation with the Shopware 6 checkout.
Recent help desk articles
Greetings! I'm Aneesh Sreedharan, CEO of 2Hats Logic Solutions. At 2Hats Logic Solutions, we are dedicated to providing technical expertise and resolving your concerns in the world of technology. Our blog page serves as a resource where we share insights and experiences, offering valuable perspectives on your queries.