diff --git a/spec/Payment/InstructionResolverSpec.php b/spec/Payment/InstructionResolverSpec.php
new file mode 100644
index 000000000..825f59cf9
--- /dev/null
+++ b/spec/Payment/InstructionResolverSpec.php
@@ -0,0 +1,54 @@
+shouldHaveType(InstructionResolver::class);
+ }
+
+ function it_implements_instruction_resolver_interface()
+ {
+ $this->shouldImplement(InstructionResolverInterface::class);
+ }
+
+ function it_returns_text_content_for_offline_payments(
+ PaymentInterface $payment,
+ PaymentMethodInterface $method,
+ GatewayConfigInterface $gatewayConfig
+ ) {
+ $payment->getMethod()->willReturn($method);
+ $method->getGatewayConfig()->willReturn($gatewayConfig);
+ $method->getInstructions()->willReturn('Please make bank transfer to PL1234 1234 1234 1234.');
+ $gatewayConfig->getFactoryName()->willReturn(InstructionResolverInterface::GATEWAY_OFFLINE);
+
+ $expectedInstruction = new Instruction();
+ $expectedInstruction->gateway = InstructionResolverInterface::GATEWAY_OFFLINE;
+ $expectedInstruction->type = InstructionResolverInterface::TYPE_TEXT;
+ $expectedInstruction->content = 'Please make bank transfer to PL1234 1234 1234 1234.';
+
+ $this->getInstruction($payment)->shouldBeLike($expectedInstruction);
+ }
+
+ function it_throws_an_exception_when_method_is_not_set(
+ PaymentInterface $payment
+ ) {
+ $payment->getMethod()->willReturn(null);
+
+ $this
+ ->shouldThrow(new \InvalidArgumentException('Payment method is not set.'))
+ ->during('getInstruction', [$payment])
+ ;
+ }
+}
diff --git a/src/Controller/Order/GetPaymentInstructionAction.php b/src/Controller/Order/GetPaymentInstructionAction.php
new file mode 100644
index 000000000..9c0d4252b
--- /dev/null
+++ b/src/Controller/Order/GetPaymentInstructionAction.php
@@ -0,0 +1,122 @@
+payum = $payum;
+ $this->orderRepository = $orderRepository;
+ $this->viewHandler = $viewHandler;
+ }
+
+ public function __invoke(Request $request): Response
+ {
+ $token = $request->attributes->get('token');
+
+ /** @var OrderInterface $order */
+ $order = $this->orderRepository->findOneByTokenValue($token);
+
+ if (null === $order) {
+ throw new NotFoundHttpException(sprintf('Order with token "%s" does not exist.', $token));
+ }
+
+ $payment = $order->getLastPayment(PaymentInterface::STATE_NEW);
+
+ if (null === $payment) {
+ throw new \LogicException(sprintf('Order with token "%s" does not have any "new" payments.', $token));
+ }
+
+ $method = $payment->getMethod();
+ $gatewayConfig = $method->getGatewayConfig();
+
+ $token = $this->provideTokenBasedOnPayment($payment);
+
+ if ('offline' === $gatewayConfig->getFactoryName()) {
+ $view = View::create([
+ 'method' => $gatewayConfig->getGatewayName(),
+ 'type' => 'text',
+ 'content' => $method->getInstructions(),
+ ]);
+
+ return $this->viewHandler->handle($view);
+ }
+
+ $gateway = $this->payum->getGateway($token->getGatewayName());
+ $gateway->execute(new Capture($token));
+ $this->payum->getHttpRequestVerifier()->invalidate($token);
+
+ $view = View::create([
+ 'method' => $gatewayConfig->getGatewayName(),
+ 'type' => 'redirect',
+ 'content' => [
+ 'url' => $token->getTargetUrl(),
+ ]
+ ]);
+
+ return $this->viewHandler->handle($view);
+ }
+
+ private function provideTokenBasedOnPayment(PaymentInterface $payment): TokenInterface
+ {
+ $method = $payment->getMethod();
+ $gatewayConfig = $method->getGatewayConfig();
+
+ $token = $this->getTokenFactory()->createCaptureToken(
+ $gatewayConfig->getGatewayName(),
+ $payment,
+ 'sylius_shop_homepage'
+ );
+
+ return $token;
+ }
+
+ private function getTokenFactory(): GenericTokenFactoryInterface
+ {
+ return $this->payum->getTokenFactory();
+ }
+}
diff --git a/src/Payment/Instruction.php b/src/Payment/Instruction.php
new file mode 100644
index 000000000..c4520bed4
--- /dev/null
+++ b/src/Payment/Instruction.php
@@ -0,0 +1,12 @@
+getMethod();
+
+ if (null === $method) {
+ throw new \InvalidArgumentException('Payment method is not set.');
+ }
+
+ $gatewayConfig = $method->getGatewayConfig();
+
+ $instruction = new Instruction();
+ $instruction->gateway = InstructionResolverInterface::GATEWAY_OFFLINE;
+ $instruction->type = InstructionResolverInterface::TYPE_TEXT;
+ $instruction->content = $method->getInstructions();
+
+ return $instruction;
+ }
+}
diff --git a/src/Payment/InstructionResolverInterface.php b/src/Payment/InstructionResolverInterface.php
new file mode 100644
index 000000000..0826f7462
--- /dev/null
+++ b/src/Payment/InstructionResolverInterface.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/config/services/controllers.xml b/src/Resources/config/services/controllers.xml
index 4b549cd68..90721aedf 100644
--- a/src/Resources/config/services/controllers.xml
+++ b/src/Resources/config/services/controllers.xml
@@ -3,6 +3,7 @@
+
diff --git a/tests/Controller/OrderPaymentApiTest.php b/tests/Controller/OrderPaymentApiTest.php
new file mode 100644
index 000000000..8cfe4a530
--- /dev/null
+++ b/tests/Controller/OrderPaymentApiTest.php
@@ -0,0 +1,110 @@
+loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml']);
+
+ $token = 'SDAOSLEFNWU35H3QLI5325';
+
+ /** @var CommandBus $bus */
+ $bus = $this->get('tactician.commandbus');
+ $bus->handle(new PickupCart($token, 'WEB_GB'));
+ $bus->handle(new PutSimpleItemToCart($token, 'LOGAN_MUG_CODE', 5));
+ $bus->handle(new AddressOrder(
+ $token,
+ Address::createFromArray([
+ 'firstName' => 'Sherlock',
+ 'lastName' => 'Holmes',
+ 'city' => 'London',
+ 'street' => 'Baker Street 221b',
+ 'countryCode' => 'GB',
+ 'postcode' => 'NWB',
+ 'provinceName' => 'Greater London',
+ ]), Address::createFromArray([
+ 'firstName' => 'Sherlock',
+ 'lastName' => 'Holmes',
+ 'city' => 'London',
+ 'street' => 'Baker Street 221b',
+ 'countryCode' => 'GB',
+ 'postcode' => 'NWB',
+ 'provinceName' => 'Greater London',
+ ])
+ ));
+
+ $bus->handle(new ChooseShippingMethod($token, 0, 'DHL'));
+ $bus->handle(new ChoosePaymentMethod($token, 0, 'PBC'));
+ $bus->handle(new CompleteOrder($token, "shop@example.com"));
+
+ $this->client->request('GET', sprintf('/shop-api/order/%s/payment-instruction', $token), [], [], [
+ 'ACCEPT' => 'application/json',
+ ]);
+
+ $response = $this->client->getResponse();
+ $this->assertResponse($response, 'order/payment_instruction_offline', Response::HTTP_OK);
+ }
+
+ /**
+ * @test
+ */
+ public function it_returns_redirect_instruction_for_hosted_payment_pages()
+ {
+ $this->loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml']);
+
+ $token = 'SDAOSLEFNWU35H3QLI5325';
+
+ /** @var CommandBus $bus */
+ $bus = $this->get('tactician.commandbus');
+ $bus->handle(new PickupCart($token, 'WEB_GB'));
+ $bus->handle(new PutSimpleItemToCart($token, 'LOGAN_MUG_CODE', 5));
+ $bus->handle(new AddressOrder(
+ $token,
+ Address::createFromArray([
+ 'firstName' => 'Sherlock',
+ 'lastName' => 'Holmes',
+ 'city' => 'London',
+ 'street' => 'Baker Street 221b',
+ 'countryCode' => 'GB',
+ 'postcode' => 'NWB',
+ 'provinceName' => 'Greater London',
+ ]), Address::createFromArray([
+ 'firstName' => 'Sherlock',
+ 'lastName' => 'Holmes',
+ 'city' => 'London',
+ 'street' => 'Baker Street 221b',
+ 'countryCode' => 'GB',
+ 'postcode' => 'NWB',
+ 'provinceName' => 'Greater London',
+ ])
+ ));
+
+ $bus->handle(new ChooseShippingMethod($token, 0, 'DHL'));
+ $bus->handle(new ChoosePaymentMethod($token, 0, 'paypal'));
+ $bus->handle(new CompleteOrder($token, "shop@example.com"));
+
+ $this->client->request('GET', sprintf('/shop-api/order/%s/payment-instruction', $token), [], [], [
+ 'ACCEPT' => 'application/json',
+ ]);
+
+ $response = $this->client->getResponse();
+ $this->assertResponse($response, 'order/payment_instruction_paypal', Response::HTTP_OK);
+ }
+}
diff --git a/tests/DataFixtures/ORM/payment.yml b/tests/DataFixtures/ORM/payment.yml
index 5e016ea6b..fd6d89d4c 100644
--- a/tests/DataFixtures/ORM/payment.yml
+++ b/tests/DataFixtures/ORM/payment.yml
@@ -11,12 +11,25 @@ Sylius\Component\Core\Model\PaymentMethod:
gatewayConfig: '@offline'
currentLocale: "en_GB"
channels: ["@gb_web_channel"]
+ pay_by_paypal:
+ code: "paypal"
+ enabled: true
+ gatewayConfig: '@paypal'
+ currentLocale: "en_GB"
+ channels: ["@gb_web_channel"]
Sylius\Bundle\PayumBundle\Model\GatewayConfig:
offline:
gatewayName: 'Offline'
factoryName: 'offline'
config: []
+ paypal:
+ gatewayName: 'PayPal Express Checkout'
+ factoryName: 'paypal_express_checkout'
+ config:
+ username: "test"
+ password: "test"
+ signature: "test"
Sylius\Component\Payment\Model\PaymentMethodTranslation:
cash_on_delivery_translation:
@@ -28,4 +41,10 @@ Sylius\Component\Payment\Model\PaymentMethodTranslation:
name: 'Pay by check'
locale: 'en_GB'
description:
+ instructions: "Please make bank transfer to: PL1234 1234 1234 1234."
translatable: "@pay_by_check"
+ paypal_translation:
+ name: 'PayPal Express Checkout'
+ locale: 'en_GB'
+ description:
+ translatable: "@pay_by_paypal"
diff --git a/tests/Responses/Expected/order/payment_instruction_offline.json b/tests/Responses/Expected/order/payment_instruction_offline.json
new file mode 100644
index 000000000..9a08db5c9
--- /dev/null
+++ b/tests/Responses/Expected/order/payment_instruction_offline.json
@@ -0,0 +1,5 @@
+{
+ "method": "Offline",
+ "type": "text",
+ "content": "Please make bank transfer to: PL1234 1234 1234 1234."
+}
diff --git a/tests/Responses/Expected/order/payment_instruction_paypal.json b/tests/Responses/Expected/order/payment_instruction_paypal.json
new file mode 100644
index 000000000..b0b396451
--- /dev/null
+++ b/tests/Responses/Expected/order/payment_instruction_paypal.json
@@ -0,0 +1,7 @@
+{
+ "method": "PayPal Express Checkout",
+ "type": "redirect",
+ "content": {
+ "url": "https://xxx"
+ }
+}