Skip to content

Commit

Permalink
feature #11754 [API] Order confirmation email sending (lchrusciel, Si…
Browse files Browse the repository at this point in the history
…rDomin)

This PR was merged into the 1.9-dev branch.

Discussion
----------

| Q               | A
| --------------- | -----
| Branch?         | master
| Bug fix?        | no
| New feature?    | yes
| BC breaks?      | no
| Deprecations?   | no
| Related tickets | 
| License         | MIT

<!--
 - Bug fixes must be submitted against the 1.7 branch (the lowest possible)
 - Features and deprecations must be submitted against the master branch
 - Make sure that the correct base branch is set

 To be sure you are not breaking any Backward Compatibilities, check the documentation:
 https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html
-->

This PR introduce mailing refactor, which should be continued in the future. Bigger description and ADR in progress.

Commits
-------

7f95827 [API] Order confirmation email sending
ffb19b6 [Core] Extract mailer sender from ShopBundle
3c98df3 [Core] Extract order confirmation templates to core
866767f [Core] Sending order confiramtion email from core
78223c1 [Core] Test order confirmation mailing
6598363 fix composer.json api platform, tests for api mailing added
4e419ae build fix
  • Loading branch information
GSadee committed Nov 16, 2020
2 parents b3bde87 + 4e419ae commit 4b1ea10
Show file tree
Hide file tree
Showing 26 changed files with 581 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ Feature: Receiving confirmation email after finalizing checkout
And the store ships everywhere for free
And the store allows paying offline

@ui @email
@ui @email @api
Scenario: Receiving confirmation email after finalizing checkout
Given I have product "Sig Sauer P226" in the cart
And I have completed addressing step with email "john@example.com" and "United States" based billing address
And I have proceeded order with "Free" shipping method and "Offline" payment
When I confirm my order
Then an email with the summary of order placed by "john@example.com" should be sent to him

@ui @email
@ui @email @api
Scenario: Receiving confirmation email after finalizing checkout in different locale than the default one
Given I have product "Sig Sauer P226" in the cart
And I have proceeded through checkout process in the "Polish (Poland)" locale with email "john@example.com"
Expand Down
45 changes: 38 additions & 7 deletions src/Sylius/Behat/Context/Setup/CheckoutContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ public function __construct(
$this->sharedStorage = $sharedStorage;
}

/**
* @Given I have proceeded through checkout process in the :localeCode locale with email :email
*/
public function iHaveProceededThroughCheckoutProcessInTheLocaleWithEmail(string $localeCode, string $email)
{
$cartToken = $this->sharedStorage->get('cart_token');

/** @var OrderInterface|null $cart */
$cart = $this->orderRepository->findCartByTokenValue($cartToken);
Assert::notNull($cart);

$cart->setLocaleCode($localeCode);

$command = new AddressOrder($email, $this->getDefaultAddress());
$command->setOrderTokenValue($cartToken);
$this->commandBus->dispatch($command);

$this->completeCheckout($cart);
}

/**
* @Given I have proceeded through checkout process
*/
Expand All @@ -75,33 +95,44 @@ public function iHaveProceededThroughCheckoutProcess(): void
$cart = $this->orderRepository->findCartByTokenValue($cartToken);
Assert::notNull($cart);

$command = new AddressOrder('rich@sylius.com', $this->getDefaultAddress());
$command->setOrderTokenValue($cartToken);
$this->commandBus->dispatch($command);

$this->completeCheckout($cart);
}

private function getDefaultAddress(): AddressInterface
{
/** @var AddressInterface $address */
$address = $this->addressFactory->createNew();

$address->setCity('New York');
$address->setStreet('Wall Street');
$address->setPostcode('00-001');
$address->setCountryCode('US');
$address->setFirstName('Richy');
$address->setLastName('Rich');

$command = new AddressOrder('rich@sylius.com', $address);
$command->setOrderTokenValue($cartToken);
$this->commandBus->dispatch($command);
return $address;
}

private function completeCheckout(OrderInterface $order): void
{
$command = new ChooseShippingMethod($this->shippingMethodRepository->findOneBy([])->getCode());
$command->setOrderTokenValue($cartToken);
$command->setOrderTokenValue($order->getTokenValue());

/** @var ShipmentInterface $shipment */
$shipment = $cart->getShipments()->first();
$shipment = $order->getShipments()->first();

$command->setSubresourceId((string) $shipment->getId());
$this->commandBus->dispatch($command);

$command = new ChoosePaymentMethod($this->paymentMethodRepository->findOneBy([])->getCode());
$command->setOrderTokenValue($cartToken);
$command->setOrderTokenValue($order->getTokenValue());

/** @var PaymentInterface $payment */
$payment = $cart->getPayments()->first();
$payment = $order->getPayments()->first();
$command->setSubresourceId((string) $payment->getId());

$this->commandBus->dispatch($command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ default:
- sylius.behat.context.setup.admin_user
- sylius.behat.context.setup.cart
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.checkout
- sylius.behat.context.setup.geographical
- sylius.behat.context.setup.customer
- sylius.behat.context.setup.geographical
- sylius.behat.context.setup.locale
Expand All @@ -49,5 +51,7 @@ default:
- sylius.behat.context.api.shop.login
- sylius.behat.context.api.shop.order

- sylius.behat.context.ui.email

filters:
tags: "@checkout && @api"
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
{% block body %}
{% autoescape false %}
{% set logo = channel.hostname is not null ? 'http://' ~ channel.hostname ~ asset('assets/admin/img/logo.png') : absolute_url(asset('assets/admin/img/logo.png')) %}

<div style="font-family: sans-serif; background: #eee; padding-top: 30px; padding-bottom: 30px; font-size: 16px; color: #333; line-height: 1.5">
<div style="max-width: 600px; margin: 20px auto;">
<div style="margin-bottom: 30px; text-align: center;">
{% if sylius_bundle_loaded_checker('SyliusShopBundle') %}
{% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_homepage', {'_locale': localeCode}) : url('sylius_shop_homepage', {'_locale': localeCode}) %}
<a href="{{ url|raw }}">
<img src="{{ logo }}" alt="Sylius" style="width: 170px">
</a>
{% else %}
<img src="{{ logo }}" alt="Sylius" style="width: 170px">
{% endif %}
</div>

<div style="background: #fff; padding: 40px 30px;">
{% block content %}{% endblock %}
</div>

<div style="text-align: center; padding: 20px 0;">
<a href="https://sylius.com" target="_blank" style="color: #1abb9c; text-decoration: none;">sylius.com</a>
</div>
</div>
</div>
{% endautoescape %}
{% endblock %}
{% extends '@SyliusCore/Email/layout.html.twig' %}
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
{% deprecated 'The "@SyliusAdmin/Email/orderConfirmation.html.twig" template is deprecated since Sylius 1.8 and will be removed in Sylius 2.0, use "@SyliusCore/Email/layout.html.twig" instead.' %}
{% extends '@SyliusAdmin/Email/layout.html.twig' %}

{% block subject %}
{{ 'sylius.email.order_confirmation.subject'|trans({}, null, localeCode) }}
{% include '@SyliusCore/Email/Blocks/OrderConfirmation/_subject.html.twig' %}
{% endblock %}

{% block content %}
{% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_order_show', {'tokenValue': order.tokenValue, '_locale': localeCode}) : url('sylius_shop_order_show', {'tokenValue': order.tokenValue, '_locale': localeCode}) %}

<div style="text-align: center; margin-bottom: 30px;">
{{ 'sylius.email.order_confirmation.your_order_number'|trans({}, null, localeCode) }}
<div style="margin: 10px 0;">
<span style="border: 1px solid #eee; padding: 10px; color: #1abb9c; font-size: 28px;">
{{ order.number }}
</span>
</div>
{{ 'sylius.email.order_confirmation.has_been_successfully_placed'|trans({}, null, localeCode) }}
</div>

<div style="text-align: center; margin-bottom: 30px;">
<a href="{{ url|raw }}" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
{{ 'sylius.email.order_confirmation.view_order_or_change_payment_method'|trans({}, null, localeCode) }}
</a>
</div>

<div style="text-align: center;">
{{ 'sylius.email.order_confirmation.thank_you'|trans({}, null, localeCode) }}
</div>
{% include '@SyliusCore/Email/Blocks/OrderConfirmation/_content.html.twig' %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use SM\Factory\FactoryInterface;
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
use Sylius\Bundle\CoreBundle\Mailer\OrderEmailManagerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\OrderCheckoutTransitions;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
Expand All @@ -30,12 +31,17 @@ final class CompleteOrderHandler implements MessageHandlerInterface
/** @var FactoryInterface */
private $stateMachineFactory;

/** @var OrderEmailManagerInterface */
private $emailManager;

public function __construct(
OrderRepositoryInterface $orderRepository,
FactoryInterface $stateMachineFactory
FactoryInterface $stateMachineFactory,
OrderEmailManagerInterface $emailManager
) {
$this->orderRepository = $orderRepository;
$this->stateMachineFactory = $stateMachineFactory;
$this->emailManager = $emailManager;
}

public function __invoke(CompleteOrder $completeOrder): OrderInterface
Expand All @@ -60,6 +66,8 @@ public function __invoke(CompleteOrder $completeOrder): OrderInterface

$stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE);

$this->emailManager->sendConfirmationEmail($cart);

return $cart;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Checkout\CompleteOrderHandler">
<argument type="service" id="sylius.repository.order"/>
<argument type="service" id="sm.factory" />
<tag name="messenger.message_handler" bus="sylius_default.bus"/>
<argument type="service" id="sylius.mailer.order_email_manager" />
<tag name="messenger.message_handler" />
</service>

<service id="Sylius\Bundle\ApiBundle\CommandHandler\Cart\ChangeItemQuantityInCartHandler">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@
use SM\Factory\FactoryInterface;
use SM\StateMachine\StateMachineInterface;
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
use Sylius\Bundle\CoreBundle\Mailer\OrderEmailManagerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\OrderCheckoutTransitions;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;

final class CompleteOrderHandlerSpec extends ObjectBehavior
{
function let(OrderRepositoryInterface $orderRepository, FactoryInterface $stateMachineFactory): void
{
$this->beConstructedWith($orderRepository, $stateMachineFactory);
function let(
OrderRepositoryInterface $orderRepository,
FactoryInterface $stateMachineFactory,
OrderEmailManagerInterface $emailManager
): void {
$this->beConstructedWith($orderRepository, $stateMachineFactory, $emailManager);
}

function it_handles_order_completion_without_notes(
OrderRepositoryInterface $orderRepository,
StateMachineInterface $stateMachine,
OrderInterface $order,
FactoryInterface $stateMachineFactory
FactoryInterface $stateMachineFactory,
OrderEmailManagerInterface $emailManager
): void {
$completeOrder = new CompleteOrder();
$completeOrder->setOrderTokenValue('ORDERTOKEN');
Expand All @@ -47,14 +52,17 @@ function it_handles_order_completion_without_notes(

$stateMachine->apply('complete')->shouldBeCalled();

$emailManager->sendConfirmationEmail($order)->shouldBeCalled();

$this($completeOrder)->shouldReturn($order);
}

function it_handles_order_completion_with_notes(
OrderRepositoryInterface $orderRepository,
StateMachineInterface $stateMachine,
OrderInterface $order,
FactoryInterface $stateMachineFactory
FactoryInterface $stateMachineFactory,
OrderEmailManagerInterface $emailManager
): void {
$completeOrder = new CompleteOrder('ThankYou');
$completeOrder->setOrderTokenValue('ORDERTOKEN');
Expand All @@ -69,6 +77,8 @@ function it_handles_order_completion_with_notes(

$stateMachine->apply('complete')->shouldBeCalled();

$emailManager->sendConfirmationEmail($order)->shouldBeCalled();

$this($completeOrder)->shouldReturn($order);
}

Expand Down
45 changes: 45 additions & 0 deletions src/Sylius/Bundle/CoreBundle/Mailer/OrderEmailManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\CoreBundle\Mailer;

use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Mailer\Sender\SenderInterface;

final class OrderEmailManager implements OrderEmailManagerInterface
{
/** @var SenderInterface */
private $emailSender;

public function __construct(SenderInterface $emailSender)
{
$this->emailSender = $emailSender;
}

public function sendConfirmationEmail(OrderInterface $order): void
{
/** @var CustomerInterface $customer */
$customer = $order->getCustomer();

$this->emailSender->send(
Emails::ORDER_CONFIRMATION,
[$customer->getEmail()],
[
'order' => $order,
'channel' => $order->getChannel(),
'localeCode' => $order->getLocaleCode(),
]
);
}
}
21 changes: 21 additions & 0 deletions src/Sylius/Bundle/CoreBundle/Mailer/OrderEmailManagerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\CoreBundle\Mailer;

use Sylius\Component\Core\Model\OrderInterface;

interface OrderEmailManagerInterface
{
public function sendConfirmationEmail(OrderInterface $order): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ sylius_mailer:
sender:
name: Example.com
address: no-reply@example.com
emails:
order_confirmation:
subject: sylius.emails.order_confirmation.subject
template: "@SyliusCore/Email/orderConfirmation.html.twig"
4 changes: 4 additions & 0 deletions src/Sylius/Bundle/CoreBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,9 @@
<service id="sylius.grid_filter.entities" class="Sylius\Component\Core\Grid\Filter\EntitiesFilter">
<tag name="sylius.grid_filter" type="entities" form-type="Sylius\Bundle\CoreBundle\Form\Type\Grid\Filter\EntitiesFilterType" />
</service>

<service class="Sylius\Bundle\CoreBundle\Mailer\OrderEmailManager" id="sylius.mailer.order_email_manager">
<argument type="service" id="sylius.email_sender" />
</service>
</services>
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div style="text-align: center; margin-bottom: 30px;">
{{ 'sylius.email.order_confirmation.your_order_number'|trans({}, null, localeCode) }}
<div style="margin: 10px 0;">
<span style="border: 1px solid #eee; padding: 10px; color: #1abb9c; font-size: 28px;">
{{ order.number }}
</span>
</div>
{{ 'sylius.email.order_confirmation.has_been_successfully_placed'|trans({}, null, localeCode) }}
</div>

{% if sylius_bundle_loaded_checker('SyliusShopBundle') %}
{% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_order_show', {'tokenValue': order.tokenValue, '_locale': localeCode}) : url('sylius_shop_order_show', {'tokenValue': order.tokenValue, '_locale': localeCode}) %}

<div style="text-align: center; margin-bottom: 30px;">
<a href="{{ url|raw }}" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
{{ 'sylius.email.order_confirmation.view_order_or_change_payment_method'|trans({}, null, localeCode) }}
</a>
</div>
{% endif %}

<div style="text-align: center;">
{{ 'sylius.email.order_confirmation.thank_you'|trans({}, null, localeCode) }}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ 'sylius.email.order_confirmation.subject'|trans({}, null, localeCode) }}
Loading

0 comments on commit 4b1ea10

Please sign in to comment.