Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@

## Usage
1. Create a webform
2. Add a Price element
3. Setup the values of the element
1. Use the price next to the values to simulate product variations
2. Use the price below to create multiple products with one price
4. Save the webform and create a submission
2. Add elements
1. Order ID (int)
2. Order Url (url)
3. Order status (string)
4. Product (options)
3. Set permissions for 'Order *'-fields for only administrators.
4. Setup the values of the element Price
1. Use the key to the option values to simulate product variations
2. Use the top price below to create multiple products with one price
5. Setup the Webform Product Handler and map the fields
6. Create a link-field (no title) in the Order Type 'Webform' called 'field_link_order_origin'.
7. Save the webform and create a submission

## Known issues
* Will only work for the default store, if no store is selected as default, it will crash
* There is no reference to the webform submission in the order
* There is no reference to the order in the webform submission
Still work in progress, please report issues at https://github.com/chx/webform_product/issues
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ id: webform
purchasableEntityType: ''
orderType: default
traits: { }
locked: false
240 changes: 240 additions & 0 deletions src/Controller/WebformProductController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<?php

namespace Drupal\webform_product\Controller;

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Url;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionInterface;
use Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
* Provides route responses for webform product.
*
* @todo Dependency injection.
*/
class WebformProductController extends ControllerBase implements ContainerInjectionInterface {

use MessengerTrait;

// Payment statuses.
const PAYMENT_STATUS_NULL = '';
const PAYMENT_STATUS_INITIALIZED = 'initialized';
const PAYMENT_STATUS_CANCELED = 'canceled';
const PAYMENT_STATUS_COMPLETED = 'completed';
const PAYMENT_STATUS_EXCEPTION = 'exception';

/**
* Complete the submission and order.
*
* @param \Drupal\webform\WebformInterface $webform
* A webform.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The Redirect response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function completedSubmission(WebformInterface $webform) {
$webform_submission = $this->getWebformSubmissionFromToken($webform);
$order = $this->getOrder();

$this->checkAccess($webform_submission, $order);

self::setSubmissionOrderStatus($webform_submission, self::PAYMENT_STATUS_COMPLETED);

// Disable the webform draft state, to mark the payment as completed.
// Set the webform 'completed' state, to trigger webform handlers such as
// Exact and Email.
$webform_submission
->set('in_draft', FALSE)
->set('completed', TRUE)
->save();

// Transition the order from 'draft' to 'validation'.
$this->placeOrder($order);

// Load confirmation page settings.
$confirmation_type = $webform_submission->getWebform()->getSetting('confirmation_type');
$has_confirmation_url = in_array($confirmation_type, [WebformInterface::CONFIRMATION_URL, WebformInterface::CONFIRMATION_URL_MESSAGE]);
$has_confirmation_message = !in_array($confirmation_type, [WebformInterface::CONFIRMATION_URL]);

$redirect_url = $webform_submission->getSourceUrl();
if ($has_confirmation_url) {
// @todo Validate url like \Drupal\webform\WebformSubmissionForm::setConfirmation().
$url = $webform_submission->getWebform()->getSetting('confirmation_url');
if ($url) {
$redirect_url = URL::fromUserInput($url);
}
}

if ($has_confirmation_message) {
$message = $webform_submission->getWebform()->getSetting('confirmation_message');
$this->messenger()->addStatus(Xss::filter($message));
}

return $this->redirectToUrl($redirect_url);
}

/**
* Cancel the submission and notify user.
*
* @param \Drupal\webform\WebformInterface $webform
* A webform.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The Redirect response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function canceledSubmission(WebformInterface $webform) {
$webform_submission = $this->getWebformSubmissionFromToken($webform);

self::setSubmissionOrderStatus($webform_submission, self::PAYMENT_STATUS_CANCELED);
$webform_submission->resave();

$this->messenger()->addWarning(t('The payment has been canceled, please re-submit the form to complete the payment.'));

return $this->redirectToUrl($webform_submission->getSourceUrl());
}

/**
* Cancel the submission, notify user and log the exception.
*
* @param \Drupal\webform\WebformInterface $webform
* A webform.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The Redirect response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function exceptionSubmission(WebformInterface $webform) {
$webform_submission = $this->getWebformSubmissionFromToken($webform);

self::setSubmissionOrderStatus($webform_submission, self::PAYMENT_STATUS_EXCEPTION);
$webform_submission->resave();

$this->messenger()->addError(t('Something went wrong, the payment has been canceled. Please try again later.'));

return $this->redirectToUrl($webform_submission->getSourceUrl());
}

/**
* Transition the order status to 'completed'.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* A order.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function placeOrder(OrderInterface $order) {
$transition = $order->getState()->getWorkflow()->getTransition('place');
$order->getState()->applyTransition($transition);

// The order is probably payed, allow editing by shop managers again.
$order->unlock();

$order->save();
}

/**
* Get the order from the current request.
*
* @return \Drupal\commerce_order\Entity\OrderInterface
* A order.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
protected function getOrder() {
$order_id = \Drupal::requestStack()->getCurrentRequest()->get('order');
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = \Drupal::entityTypeManager()
->getStorage('commerce_order')
->load($order_id);
return $order;
}

/**
* Get webform submission from query token.
*
* @param \Drupal\webform\WebformInterface $webform
* The webform, related to the token.
*
* @return \Drupal\webform\WebformSubmissionInterface|null
* A submission loaded from the token.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
protected function getWebformSubmissionFromToken(WebformInterface $webform) {
/** @var \Drupal\webform\WebformSubmissionStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('webform_submission');

$token = \Drupal::requestStack()->getCurrentRequest()->get('submission');
if (!$token) {
throw new AccessDeniedHttpException('Token not found.');
}

$webform_submission = $storage->loadFromToken($token, $webform);
if (!$webform_submission) {
throw new AccessDeniedHttpException('Webform submission failed to load.');
}

return $webform_submission;
}

/**
* Check if the submission and order.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* A order.
*/
protected function checkAccess(WebformSubmissionInterface $webform_submission, OrderInterface $order) {
if (!$order || !$webform_submission->isDraft() || $order->getState()->value == 'completed') {
throw new AccessDeniedHttpException('Submission already completed.');
}
}

/**
* Redirect to the given Url.
*
* @param \Drupal\Core\URL $url
* Url to redirect to.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The Redirect response.
*/
protected function redirectToUrl(URL $url) {
return $this->redirect($url->getRouteName(), $url->getRouteParameters());
}

/**
* Set the Order status in the Submission.
*
* @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
* The webform Submission.
* @param string $status
* The status to store in the Submission.
*/
public static function setSubmissionOrderStatus(WebformSubmissionInterface $webformSubmission, $status) {
$handlers = $webformSubmission->getWebform()->getHandlers('webform_product');

$config = $handlers->getConfiguration();
/** @var \Drupal\webform\Plugin\WebformHandlerInterface $handler */
$handler = reset($config);
$settings = $handler['settings'];

if ($settings[WebformProductWebformHandler::FIELD_STATUS]) {
$webformSubmission->setElementData($settings[WebformProductWebformHandler::FIELD_STATUS], $status);
}
}

}
91 changes: 91 additions & 0 deletions src/EventSubscriber/OrderEventSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace Drupal\webform_product\EventSubscriber;

use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Url;
use Drupal\state_machine\Event\WorkflowTransitionEvent;
use Drupal\webform_product\Controller\WebformProductController;
use Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Class OrderEventSubscriber.
*
* @package Drupal\webform_product\ProductEventSubscriber
*/
class OrderEventSubscriber implements EventSubscriberInterface {

use LoggerChannelTrait;

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = [
'commerce_order.validate.post_transition' => ['onOrderValidatePostTransition'],
];
return $events;
}

/**
* Post Transition; Place (from Draft to Validation).
*
* Execute Webform Submission Handlers on Validate transition, when a payment
* has been validated by the payment provider.
*
* This will only be triggered if the submission is initialized.
*
* @todo Add validate state to the submission status field.
* @todo Make use of the workflow labels, instead of custom labels.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The event.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function onOrderValidatePostTransition(WorkflowTransitionEvent $event) {
$order = $event->getEntity();

if (!$order->hasField(WebformProductWebformHandler::FIELD_LINK_ORDER_ORIGIN)) {
return;
}

$source_uri = $order->get(WebformProductWebformHandler::FIELD_LINK_ORDER_ORIGIN)->getValue();
$params = Url::fromUri($source_uri[0]['uri'])->getRouteParameters();

/** @var \Drupal\webform\WebformSubmissionInterface $webformSubmission */
$webformSubmission = \Drupal::entityTypeManager()->getStorage('webform_submission')->load($params['webform_submission']);
$handlers = $webformSubmission->getWebform()->getHandlers('webform_product');

$config = $handlers->getConfiguration();
if (!$config) {
return;
}

/** @var \Drupal\webform\Plugin\WebformHandlerInterface $handler */
$handler = reset($config);
$settings = $handler['settings'];

$status = $webformSubmission->getElementData($settings[WebformProductWebformHandler::FIELD_STATUS]);

// Complete submission if this hasn't been done.
// There is no need for an access check, because the transition will check.
if ($status && $status === WebformProductController::PAYMENT_STATUS_INITIALIZED) {

// Disable the webform draft state, to mark the payment as completed.
// Set the webform 'completed' state, to trigger webform handlers such as
// Exact and Email.
$webformSubmission
->setElementData($settings[WebformProductWebformHandler::FIELD_STATUS], WebformProductController::PAYMENT_STATUS_COMPLETED)
->set('in_draft', FALSE)
->set('completed', TRUE)
->save();

$this->getLogger('webform_product')->notice('Finalized Webform Submission %sid on payment', [
'%sid' => $webformSubmission->id(),
]);
}
}

}
Loading