Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.
63 changes: 61 additions & 2 deletions doc/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ paths:
name: "content"
required: true
schema:
$ref: "#/definitions/CheckoutChoosePaymentMethodRequest"
$ref: "#/definitions/ChoosePaymentMethodRequest"
responses:
204:
description: "Payment method has been chosen."
Expand Down Expand Up @@ -838,6 +838,51 @@ paths:
security:
- bearerAuth: []

/orders/{token}/payment:
parameters:
- $ref: "#/parameters/CartToken"
get:
tags:
- "order"
summary: "Get available payment methods."
description: "This endpoint will show you available payment methods for an order."
operationId: "showAvailablePaymentMethods"
responses:
200:
description: "Get available payment methods."
schema:
$ref: "#/definitions/AvailablePaymentMethods"
400:
description: "Invalid input, validation failed."
schema:
$ref: "#/definitions/GeneralError"
/orders/{token}/payment/{id}:
parameters:
- $ref: "#/parameters/CartToken"
put:
tags:
- "order"
summary: "Choosing cart payment method."
description: "This endpoint will allow you to update an order payment method."
operationId: "updatePaymentMethod"
parameters:
- name: "id"
in: "path"
description: "Order number of payment for which payment method should be specified."
required: true
type: "string"
- in: "body"
name: "content"
required: true
schema:
$ref: "#/definitions/ChoosePaymentMethodRequest"
responses:
204:
description: "Payment method has been chosen."
400:
description: "Invalid input, validation failed."
schema:
$ref: "#/definitions/GeneralError"
/me:
get:
tags:
Expand Down Expand Up @@ -1116,7 +1161,7 @@ definitions:
type: "string"
description: "Code of chosen shipping method."
example: "DHL"
CheckoutChoosePaymentMethodRequest:
ChoosePaymentMethodRequest:
type: "object"
description: "Body of request used to choose payment method."
required:
Expand Down Expand Up @@ -1808,6 +1853,20 @@ definitions:
description: "Date the order was completed in ISO 8601 format."
type: "string"
format: "date-time"
paymentState:
description: "Current payment state of an order."
type: "string"
example: "awaiting_payment"
enum:
- "cart"
- "awaiting_payment"
- "partially_authorized"
- "authorized"
- "partially_paid"
- "cancelled"
- "paid"
- "partially_refunded"
- "refunded"
items:
type: "array"
items:
Expand Down
30 changes: 30 additions & 0 deletions spec/Command/Order/UpdatePaymentMethodSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace spec\Sylius\ShopApiPlugin\Command\Order;

use PhpSpec\ObjectBehavior;

final class UpdatePaymentMethodSpec extends ObjectBehavior
{
function let(): void
{
$this->beConstructedWith('ORDERTOKEN', 1, 'CASH_ON_DELIVERY_METHOD');
}

function it_has_order_token(): void
{
$this->orderToken()->shouldReturn('ORDERTOKEN');
}

function it_has_identifier_of_payment(): void
{
$this->paymentId()->shouldReturn(1);
}

function it_has_payment_method_defined(): void
{
$this->paymentMethodCode()->shouldReturn('CASH_ON_DELIVERY_METHOD');
}
}
3 changes: 3 additions & 0 deletions spec/Factory/Order/PlacedOrderViewFactorySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\OrderCheckoutStates;
use Sylius\Component\Core\OrderPaymentStates;
use Sylius\ShopApiPlugin\Factory\AddressBook\AddressViewFactoryInterface;
use Sylius\ShopApiPlugin\Factory\Cart\AdjustmentViewFactoryInterface;
use Sylius\ShopApiPlugin\Factory\Cart\CartItemViewFactoryInterface;
Expand Down Expand Up @@ -64,6 +65,7 @@ function it_creates_a_placed_order_view(
$cart->getCurrencyCode()->willReturn('GBP');
$cart->getCheckoutState()->willReturn(OrderCheckoutStates::STATE_COMPLETED);
$cart->getCheckoutCompletedAt()->willReturn(new \DateTime('2019-02-15T15:00:00+00:00'));
$cart->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT);
$cart->getTokenValue()->willReturn('ORDER_TOKEN');
$cart->getNumber()->willReturn('ORDER_NUMBER');
$cart->getShippingTotal()->willReturn(500);
Expand Down Expand Up @@ -99,6 +101,7 @@ function it_creates_a_placed_order_view(
$placedOrderView->locale = 'en_GB';
$placedOrderView->checkoutState = OrderCheckoutStates::STATE_COMPLETED;
$placedOrderView->checkoutCompletedAt = '2019-02-15T15:00:00+00:00';
$placedOrderView->paymentState = OrderPaymentStates::STATE_AWAITING_PAYMENT;

$placedOrderView->items = [new ItemView()];
$placedOrderView->totals = new TotalsView();
Expand Down
168 changes: 168 additions & 0 deletions spec/Handler/Order/UpdatePaymentMethodHandlerSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

declare(strict_types=1);

namespace spec\Sylius\ShopApiPlugin\Handler\Order;

use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\Component\Core\OrderPaymentStates;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
use Sylius\ShopApiPlugin\Command\Order\UpdatePaymentMethod;

final class UpdatePaymentMethodHandlerSpec extends ObjectBehavior
{
function let(
OrderRepositoryInterface $orderRepository,
PaymentMethodRepositoryInterface $paymentMethodRepository
): void {
$this->beConstructedWith($orderRepository, $paymentMethodRepository);
}

function it_assigns_chosen_payment_method_to_specified_payment(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentMethodInterface $paymentMethod,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_NEW);
$order->getPayments()->willReturn(new ArrayCollection([$payment->getWrappedObject()]));
$order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT);
$paymentMethodRepository->findOneBy(['code' => 'CASH_ON_DELIVERY_METHOD'])->willReturn($paymentMethod);
$payment->setMethod($paymentMethod)->shouldBeCalled();
$payment->getState()->willReturn(PaymentInterface::STATE_NEW);

$this(new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'));
}

function it_throws_an_exception_if_order_with_given_token_has_not_been_found(
OrderRepositoryInterface $orderRepository,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn(null);
$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}

function it_throws_an_exception_if_order_is_cart(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_CART);

$paymentMethodRepository->findOneBy(Argument::any())->shouldNotBeCalled();
$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}

function it_throws_an_exception_if_order_cannot_have_payment_updated(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_NEW);
$order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID);

$paymentMethodRepository->findOneBy(Argument::any())->shouldNotBeCalled();
$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}

function it_throws_an_exception_if_payment_method_with_given_code_has_not_been_found(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_NEW);
$order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT);
$paymentMethodRepository->findOneBy(['code' => 'CASH_ON_DELIVERY_METHOD'])->willReturn(null);

$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}

function it_throws_an_exception_if_ordered_payment_has_not_been_found(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentMethodInterface $paymentMethod,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_NEW);
$order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT);
$paymentMethodRepository->findOneBy(['code' => 'CASH_ON_DELIVERY_METHOD'])->willReturn($paymentMethod);
$order->getPayments()->willReturn(new ArrayCollection([]));

$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}

function it_throws_an_exception_if_ordered_payment_is_not_new(
OrderRepositoryInterface $orderRepository,
OrderInterface $order,
PaymentMethodRepositoryInterface $paymentMethodRepository,
PaymentMethodInterface $paymentMethod,
PaymentInterface $payment
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order);
$order->getState()->willReturn(OrderInterface::STATE_NEW);
$order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT);
$paymentMethodRepository->findOneBy(['code' => 'CASH_ON_DELIVERY_METHOD'])->willReturn($paymentMethod);
$order->getPayments()->willReturn(new ArrayCollection([$payment->getWrappedObject()]));

$payment->getState()->willReturn(PaymentInterface::STATE_CART);
$payment->setMethod(Argument::type(PaymentMethodInterface::class))->shouldNotBeCalled();

$this
->shouldThrow(\InvalidArgumentException::class)
->during('__invoke', [
new UpdatePaymentMethod('ORDERTOKEN', 0, 'CASH_ON_DELIVERY_METHOD'),
])
;
}
}
63 changes: 63 additions & 0 deletions spec/Validator/Order/OrderExistsValidatorSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace spec\Sylius\ShopApiPlugin\Validator\Order;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\ShopApiPlugin\Validator\Constraints\OrderExists;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

class OrderExistsValidatorSpec extends ObjectBehavior
{
function let(ExecutionContextInterface $executionContext, OrderRepositoryInterface $orderRepository): void
{
$this->beConstructedWith($orderRepository);

$this->initialize($executionContext);
}

function it_does_not_add_constraint_if_order_exists(
OrderInterface $order,
OrderRepositoryInterface $orderRepository,
ExecutionContextInterface $executionContext
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN', 'state' => OrderInterface::STATE_NEW])->willReturn($order);

$executionContext->addViolation(Argument::any())->shouldNotBeCalled();

$this->validate('ORDERTOKEN', new OrderExists(['state' => OrderInterface::STATE_NEW]));
}

function it_does_not_add_constraint_if_order_exists_multi_state(
OrderInterface $order,
OrderRepositoryInterface $orderRepository,
ExecutionContextInterface $executionContext
): void {
$orderRepository->findOneBy(
[
'tokenValue' => 'ORDERTOKEN',
'state' => [OrderInterface::STATE_NEW, 'other_state'],
])->willReturn($order);

$executionContext->addViolation(Argument::any())->shouldNotBeCalled();

$this->validate('ORDERTOKEN', new OrderExists(
['state' => [OrderInterface::STATE_NEW, 'other_state']]
));
}

function it_adds_constraint_if_order_does_not_exists(
OrderRepositoryInterface $orderRepository,
ExecutionContextInterface $executionContext
): void {
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN', 'state' => OrderInterface::STATE_NEW])->willReturn(null);

$executionContext->addViolation('sylius.shop_api.order.not_exists')->shouldBeCalled();

$this->validate('ORDERTOKEN', new OrderExists(['state' => OrderInterface::STATE_NEW]));
}
}
Loading