diff --git a/README b/README
index abcc1d7..7a72e23 100644
--- a/README
+++ b/README
@@ -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
diff --git a/config/install/commerce_order.commerce_order_item_type.webform.yml b/config/install/commerce_order.commerce_order_item_type.webform.yml
index 94501b6..d274d1e 100644
--- a/config/install/commerce_order.commerce_order_item_type.webform.yml
+++ b/config/install/commerce_order.commerce_order_item_type.webform.yml
@@ -6,3 +6,4 @@ id: webform
purchasableEntityType: ''
orderType: default
traits: { }
+locked: false
diff --git a/src/Controller/WebformProductController.php b/src/Controller/WebformProductController.php
new file mode 100644
index 0000000..bec25a5
--- /dev/null
+++ b/src/Controller/WebformProductController.php
@@ -0,0 +1,240 @@
+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);
+ }
+ }
+
+}
diff --git a/src/EventSubscriber/OrderEventSubscriber.php b/src/EventSubscriber/OrderEventSubscriber.php
new file mode 100644
index 0000000..9dd1f52
--- /dev/null
+++ b/src/EventSubscriber/OrderEventSubscriber.php
@@ -0,0 +1,91 @@
+ ['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(),
+ ]);
+ }
+ }
+
+}
diff --git a/src/Plugin/WebformHandler/WebformProductWebformHandler.php b/src/Plugin/WebformHandler/WebformProductWebformHandler.php
new file mode 100644
index 0000000..ddb72b5
--- /dev/null
+++ b/src/Plugin/WebformHandler/WebformProductWebformHandler.php
@@ -0,0 +1,785 @@
+entityTypeManager = $entity_type_manager;
+ $this->token = $token;
+ $this->tokenManager = $token_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('logger.factory'),
+ $container->get('config.factory'),
+ $container->get('entity_type.manager'),
+ $container->get('webform_submission.conditions_validator'),
+ $container->get('token'),
+ $container->get('webform.token_manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return [
+ self::COMMERCE_STORE => NULL,
+ self::COMMERCE_ORDER_TYPE => self::DEFAULT_ORDER_TYPE,
+ self::COMMERCE_ORDER_ITEM_TITLE => self::DEFAULT_ORDER_ITEM_TITLE,
+ self::COMMERCE_ORDER_ITEM_TYPE => self::DEFAULT_ORDER_ITEM_TYPE,
+ 'route' => 'commerce_checkout.form',
+ self::COMMERCE_CHECKOUT_STEP => self::DEFAULT_CHECKOUT_STEP,
+ self::COMMERCE_GATEWAY => NULL,
+ self::COMMERCE_METHOD => NULL,
+ self::ORDER_PRICE => NULL,
+ self::FIELD_STATUS => NULL,
+ self::FIELD_ORDER_ID => NULL,
+ self::FIELD_ORDER_URL => NULL,
+ self::FIELD_TOTAL_PRICE => NULL,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @todo Create debug mode setting.
+ * @todo Create field mapping for Billing information (name, address & mail).
+ * @todo Create more route choices.
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $configuration = $this->getConfiguration();
+ $settings = $configuration['settings'];
+
+ $form['commerce'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Commerce'),
+ ];
+ $form['commerce'][self::COMMERCE_STORE] = [
+ '#type' => 'select',
+ '#title' => $this->t('Store'),
+ '#options' => $this->getEntityOptions('commerce_store'),
+ '#default_value' => $settings[self::COMMERCE_STORE],
+ '#required' => TRUE,
+ ];
+ $form['commerce'][self::COMMERCE_ORDER_TYPE] = [
+ '#type' => 'select',
+ '#title' => $this->t('Order type'),
+ '#options' => $this->getEntityOptions('commerce_order_type'),
+ '#default_value' => $settings[self::COMMERCE_ORDER_TYPE],
+ '#required' => TRUE,
+ ];
+ $form['commerce'][self::COMMERCE_ORDER_ITEM_TYPE] = [
+ '#type' => 'select',
+ '#title' => $this->t('Order item type'),
+ '#options' => $this->getEntityOptions('commerce_order_item_type'),
+ '#default_value' => $settings[self::COMMERCE_ORDER_ITEM_TYPE],
+ '#required' => TRUE,
+ ];
+ $form['commerce'][self::COMMERCE_ORDER_ITEM_TITLE] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Order item title'),
+ '#description' => $this->t('Default %default.', ['%default' => self::DEFAULT_ORDER_ITEM_TITLE]),
+ '#default_value' => $settings[self::COMMERCE_ORDER_ITEM_TITLE],
+ '#required' => TRUE,
+ ];
+ $form['commerce'][self::COMMERCE_GATEWAY] = [
+ '#type' => 'select',
+ '#title' => $this->t('Payment provider'),
+ '#options' => $this->getEntityOptions('commerce_payment_gateway', [
+ 'status' => TRUE,
+ ]),
+ '#default_value' => $settings[self::COMMERCE_GATEWAY],
+ '#required' => TRUE,
+ ];
+
+ $token_types = ['webform', 'webform_submission'];
+ // Show webform role tokens if they have been specified.
+ if (!empty($roles_element_options)) {
+ $token_types[] = 'webform_role';
+ }
+ $form['commerce']['token_tree_link'] = $this->tokenManager->buildTreeLink(
+ $token_types,
+ $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values.')
+ );
+
+ $form['order_data'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Data to create order'),
+ ];
+ $form['order_data']['info'] = [
+ '#markup' => '
' . $this->t('Use this price field as the price of a single order item. Leave it empty to use individual webform elements with a Price field, where one order item is created per form element.') . '
',
+ ];
+
+ $field_types = ['number', 'numeric', 'textfield', 'webform_computed_twig'];
+ $form['order_data'][self::ORDER_PRICE] = [
+ '#type' => 'select',
+ '#title' => $this->t('Total price'),
+ '#options' => $this->getElementsSelectOptions($field_types),
+ '#default_value' => $settings[self::ORDER_PRICE],
+ '#empty_value' => '',
+ '#required' => FALSE,
+ '#description' => $this->t('Field types allowed: @types.', ['@types' => implode(', ', $field_types)]),
+ ];
+
+ $form['field_mapping'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Field mapping'),
+ ];
+
+ $field_types = ['textfield'];
+ $form['field_mapping'][self::FIELD_STATUS] = [
+ '#type' => 'select',
+ '#title' => $this->t('Payment status'),
+ '#options' => $this->getElementsSelectOptions($field_types),
+ '#default_value' => $settings[self::FIELD_STATUS],
+ '#empty_value' => '',
+ '#required' => TRUE,
+ '#description' => $this->t('Field types allowed: @types.', ['@types' => implode(', ', $field_types)]),
+ ];
+
+ $field_types = ['number', 'numeric', 'textfield'];
+ $form['field_mapping'][self::FIELD_ORDER_ID] = [
+ '#type' => 'select',
+ '#title' => $this->t('Order ID'),
+ '#options' => $this->getElementsSelectOptions($field_types),
+ '#default_value' => $settings[self::FIELD_ORDER_ID],
+ '#empty_value' => '',
+ '#required' => TRUE,
+ '#description' => $this->t('Field types allowed: @types.', ['@types' => implode(', ', $field_types)]),
+ ];
+
+ $field_types = ['url'];
+ $form['field_mapping'][self::FIELD_ORDER_URL] = [
+ '#type' => 'select',
+ '#title' => $this->t('Order URL'),
+ '#options' => $this->getElementsSelectOptions($field_types),
+ '#default_value' => $settings[self::FIELD_ORDER_URL],
+ '#empty_value' => '',
+ '#required' => TRUE,
+ '#description' => $this->t('Field types allowed: @types.', ['@types' => implode(', ', $field_types)]),
+ ];
+
+ $field_types = ['number', 'numeric', 'textfield'];
+ $form['field_mapping'][self::FIELD_TOTAL_PRICE] = [
+ '#type' => 'select',
+ '#title' => $this->t('Total price'),
+ '#options' => $this->getElementsSelectOptions($field_types),
+ '#default_value' => $settings[self::FIELD_TOTAL_PRICE],
+ '#empty_value' => '',
+ '#required' => FALSE,
+ '#description' => $this->t('Field types allowed: @types.', ['@types' => implode(', ', $field_types)]) . '
' . $this->t('Use this if you want to safe the total order amount to a specific field.'),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @todo Set mapped webform order and payment field permissions to 'view-only'.
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
+ parent::applyFormStateToConfiguration($form_state);
+
+ $values = $form_state->getValues();
+
+ foreach ($values['commerce'] as $key => $value) {
+ $this->configuration[$key] = $value;
+ }
+
+ foreach ($values['order_data'] as $key => $value) {
+ $this->configuration[$key] = $value;
+ }
+
+ foreach ($values['order_result'] as $key => $value) {
+ $this->configuration[$key] = $value;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) {
+ if ($update == TRUE) {
+ return;
+ }
+
+ try {
+ $orderItems = $this->getOrderItems($webform_submission);
+ if (empty($orderItems)) {
+ return;
+ }
+
+ /** @var \Drupal\commerce_cart\CartProviderInterface $cartProvider */
+ $cartProvider = \Drupal::service('commerce_cart.cart_provider');
+ /** @var \Drupal\commerce_order\Entity\OrderInterface $cartOrder */
+ $cartOrder = $this->getCart($cartProvider, $this->getStore(), TRUE);
+
+ // Fill the Cart.
+ foreach ($orderItems as $orderItem) {
+ $orderItem->save();
+ $cartOrder->addItem($orderItem);
+ }
+ $cartOrder->save();
+ $cartOrder = $this->entityTypeManager->getStorage('commerce_order')->load($cartOrder->id());
+
+ // Save the Cart (Order) with Submission data.
+ $this->setOrderCheckoutProcess($cartOrder);
+ $this->setOrderLinkReference($cartOrder, $webform_submission);
+ $this->setOrderCustomer($cartOrder);
+
+ // Save the submission with Cart data.
+ $this->setSubmissionTotalPrice($webform_submission, $cartOrder);
+ WebformProductController::setSubmissionOrderStatus($webform_submission, WebformProductController::PAYMENT_STATUS_INITIALIZED);
+ $this->setSubmissionOrderReference($webform_submission, $cartOrder);
+ $webform_submission->set('in_draft', TRUE);
+ $webform_submission->resave();
+
+ // Protect order from adding new products.
+ $cartOrder->lock();
+
+ $cartOrder->save();
+
+ // Reload the order.
+ $cartOrder = $this->entityTypeManager->getStorage('commerce_order')->load($cartOrder->id());
+
+ $this->redirectToCheckout($cartOrder);
+ }
+ catch (\Exception $e) {
+ $this->loggerFactory->get($this->pluginId)->error($e->getMessage());
+ }
+ }
+
+ /**
+ * Get option list of Entities.
+ *
+ * @param string $entity_type
+ * The entity type to load.
+ * @param array $properties
+ * The loaded entity condtions.
+ *
+ * @return array
+ * List with ids as key and label as value.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ */
+ protected function getEntityOptions($entity_type, array $properties = []) {
+ $options = [];
+
+ /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways */
+ $entities = $this->entityTypeManager
+ ->getStorage($entity_type)
+ ->loadByProperties($properties);
+
+ foreach ($entities as $entity) {
+ $options[$entity->id()] = $entity->label();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Gather all Order Items from the webform Submission.
+ *
+ * @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
+ * The webform submission.
+ *
+ * @return \Drupal\commerce_order\Entity\OrderItemInterface[]
+ * List of Order Items.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ */
+ protected function getOrderItems(WebformSubmissionInterface $webformSubmission) {
+ $price_fields = $this->getWebform()->getThirdPartySettings($this->pluginId);
+
+ // No prices, no Order.
+ if (!$price_fields) {
+ return [];
+ }
+
+ $payment_status = $this->getSavedPaymentStatus($webformSubmission);
+
+ // Create only an order for new webform submissions.
+ if ($payment_status != WebformProductController::PAYMENT_STATUS_NULL) {
+ return [];
+ }
+
+ /** @var \Drupal\commerce_store\Entity\StoreInterface $store */
+ $store = $this->getStore();
+ $currencyCode = $store->getDefaultCurrency()->getCurrencyCode();
+
+ $orderItems = [];
+ $configuration = $this->getConfiguration();
+ $settings = $configuration['settings'];
+
+ // @todo Make this also available for multiple elements.
+ $order_item_title = $this->tokenManager->replace($settings[self::COMMERCE_ORDER_ITEM_TITLE], $webformSubmission, [
+ 'webform' => $this->getWebform(),
+ ]);
+
+ if ($this->useElementBasedOrder()) {
+ // Create Order Item for each:
+ // - element option with a price.
+ // - element with a top price.
+ foreach ($webformSubmission->getData() as $key => $value) {
+ if (empty($price_fields[$key])) {
+ continue;
+ }
+
+ // Element with 'top'.
+ if (!empty($price_fields[$key]['top'])) {
+ $orderItems[] = OrderItem::create([
+ 'type' => $this->configuration[self::COMMERCE_ORDER_ITEM_TYPE],
+ 'title' => $order_item_title,
+ 'quantity' => 1,
+ 'unit_price' => [
+ 'number' => $price_fields[$key]['top'],
+ 'currency_code' => $currencyCode,
+ ],
+ ]);
+ }
+
+ if (!empty($price_fields[$key]['options'])) {
+ // Fix for when value is not an array.
+ if (!is_array($value)) {
+ $value_to_validate = [$value];
+ }
+ else {
+ $value_to_validate = $value;
+ }
+
+ $options = array_keys($price_fields[$key]['options']);
+ $price_options = array_intersect($value_to_validate, $options);
+ $has_other = $this->getWebform()->getElement($key);
+
+ // Other values.
+ if (isset($has_other['#other_type']) && $has_other['#other__type'] == 'number' && empty($price_options)) {
+ $orderItems[] = OrderItem::create([
+ 'type' => $this->configuration[self::COMMERCE_ORDER_ITEM_TYPE],
+ 'title' => $order_item_title,
+ 'quantity' => 1,
+ 'unit_price' => [
+ 'number' => $value,
+ 'currency_code' => $currencyCode,
+ ],
+ ]);
+ }
+ else {
+ // Option elements with price as option (checkboxes or radios).
+ foreach ($price_options as $option) {
+ $orderItems[] = OrderItem::create([
+ 'type' => $this->configuration[self::COMMERCE_ORDER_ITEM_TYPE],
+ 'title' => $order_item_title,
+ 'quantity' => 1,
+ 'unit_price' => [
+ 'number' => $price_fields[$key]['options'][$option],
+ 'currency_code' => $currencyCode,
+ ],
+ ]);
+ }
+ }
+ }
+ }
+ }
+ else {
+ $orderItems = [];
+ $price = $this->formatPrice($webformSubmission->getElementData($settings[self::ORDER_PRICE]));
+ if ($price > 0) {
+ $orderItems[] = OrderItem::create([
+ 'type' => $this->configuration[self::COMMERCE_ORDER_ITEM_TYPE],
+ 'title' => $order_item_title,
+ 'quantity' => 1,
+ 'unit_price' => [
+ 'number' => $price,
+ 'currency_code' => $currencyCode,
+ ],
+ ]);
+ }
+ }
+
+ return $orderItems;
+ }
+
+ /**
+ * Determine if Element Based order must be used.
+ *
+ * The commerce order is either created with one order item per priced field
+ * or with one item based on a single field value. The latter is usually a
+ * calculated value or the result of a (if/else) condition.
+ *
+ * @return bool
+ * Returns true if element based orders are used.
+ */
+ private function useElementBasedOrder() {
+ return empty($this->configuration[self::ORDER_PRICE]);
+ }
+
+ /**
+ * Get webform elements selectors as options.
+ *
+ * @param array $types
+ * List of types to filter.
+ * - Leave empty skip filtering of types.
+ *
+ * @see \Drupal\webform\Entity\Webform::getElementsSelectorOptions()
+ *
+ * @return array
+ * Webform elements selectors as options.
+ */
+ private function getElementsSelectOptions(array $types = []) {
+ $options = [];
+ $elements = $this->getWebform()->getElementsInitializedAndFlattened();
+ foreach ($elements as $key => $element) {
+ // Skip element if not in given 'types' array.
+ if ($types && !in_array($element['#type'], $types)) {
+ continue;
+ }
+
+ $options[$key] = $element['#title'];
+ }
+ return $options;
+ }
+
+ /**
+ * Get the payment status of the submission.
+ *
+ * - Nothing if there isn't any payment at all.
+ * - Initilized for started, but not completed payments.
+ * - Canceled for payments canceled by the user.
+ * - Exception for payments canceled by the provider.
+ *
+ * @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
+ * The webform submission.
+ *
+ * @return string
+ * The status of the payment.
+ *
+ * @see \Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler::PAYMENT_STATUS_NULL;
+ * @see \Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler::PAYMENT_STATUS_INITIALIZED;\
+ * @see \Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler::PAYMENT_STATUS_CANCELED;
+ * @see \Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler::PAYMENT_STATUS_COMPLETED;
+ * @see \Drupal\webform_product\Plugin\WebformHandler\WebformProductWebformHandler::PAYMENT_STATUS_EXCEPTION;
+ */
+ private function getSavedPaymentStatus(WebformSubmissionInterface $webformSubmission) {
+ $value = $webformSubmission->getElementData($this->configuration[self::FIELD_STATUS]);
+
+ return $value;
+ }
+
+ /**
+ * Set total price of the Order in the Submission.
+ *
+ * @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
+ * The webform Submission.
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ */
+ protected function setSubmissionTotalPrice(WebformSubmissionInterface $webformSubmission, OrderInterface $order) {
+ // Save Total price of order.
+ if ($this->configuration[self::FIELD_TOTAL_PRICE]) {
+ $total = $order->getTotalPrice()->getNumber();
+ $webformSubmission->setElementData($this->configuration[self::FIELD_TOTAL_PRICE], $total);
+ }
+ }
+
+ /**
+ * Set the Order reference in the webform Submission.
+ *
+ * @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
+ * The webform Submission.
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ *
+ * @throws \Drupal\Core\Entity\EntityMalformedException
+ */
+ protected function setSubmissionOrderReference(WebformSubmissionInterface $webformSubmission, OrderInterface $order) {
+ // Save order id to the webform for back reference.
+ if ($this->configuration[self::FIELD_ORDER_ID]) {
+ $webformSubmission
+ ->setElementData($this->configuration[self::FIELD_ORDER_ID], $order->id());
+ }
+ if ($this->configuration[self::FIELD_ORDER_URL]) {
+ $order_url = $order->toUrl()->toString();
+ $webformSubmission
+ ->setElementData($this->configuration[self::FIELD_ORDER_URL], $order_url);
+ }
+ }
+
+ /**
+ * Redirect to the configured checkout step in the Checkout Flow.
+ *
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ */
+ protected function redirectToCheckout(OrderInterface $order) {
+ // Redirect to checkout process.
+ $response = new RedirectResponse(Url::fromRoute($this->configuration['route'], [
+ 'commerce_order' => $order->id(),
+ 'step' => $this->configuration[self::COMMERCE_CHECKOUT_STEP],
+ ])->toString());
+
+ $request = \Drupal::request();
+ // Save the session.
+ $request->getSession()->save();
+ $response->prepare($request);
+ // Trigger kernel events.
+ \Drupal::service('kernel')->terminate($request, $response);
+
+ $response->send();
+ exit();
+ }
+
+ /**
+ * Set Customer data for Order.
+ *
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ *
+ * @todo Create full commerce profile for order with address and mail info.
+ */
+ protected function setOrderCustomer(OrderInterface $order) {
+ $billing_profile = Profile::create([
+ 'uid' => 0,
+ 'type' => 'customer',
+ ]);
+ $billing_profile->save();
+
+ // Add profile information.
+ $order->setBillingProfile($billing_profile);
+ }
+
+ /**
+ * Save back reference to the webform as link.
+ *
+ * Order info can't be referenced, if the referenced entity doesn't have the
+ * same lifespan.
+ *
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ * @param \Drupal\webform\WebformSubmissionInterface $webformSubmission
+ * The webform submission.
+ *
+ * @todo Make field FIELD_LINK_ORDER_ORIGIN configurable.
+ *
+ * @throws \Drupal\Core\Entity\EntityMalformedException
+ */
+ protected function setOrderLinkReference(OrderInterface $order, WebformSubmissionInterface $webformSubmission) {
+ if ($order->hasField(self::FIELD_LINK_ORDER_ORIGIN)) {
+ $uri = $webformSubmission->toUrl()->toUriString();
+ $order->set(self::FIELD_LINK_ORDER_ORIGIN, $uri);
+ }
+ }
+
+ /**
+ * Set Checkout Process variables for Order.
+ *
+ * @param \Drupal\commerce_order\Entity\OrderInterface $order
+ * The Order.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ */
+ protected function setOrderCheckoutProcess(OrderInterface $order) {
+ /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
+ $payment_gateway = $this->entityTypeManager->getStorage('commerce_payment_gateway')->load($this->configuration[self::COMMERCE_GATEWAY]);
+
+ if (!$payment_gateway) {
+ $this->loggerFactory->get($this->pluginId)->error(t('Failed to get a Payment Gateway'));
+ return;
+ }
+
+ $payment_method = empty($this->configuration[self::COMMERCE_METHOD]) ? NULL : $this->configuration[self::COMMERCE_METHOD];
+
+ // Save additional info to the order to speedup the checkout progress.
+ $order
+ ->set(self::COMMERCE_CHECKOUT_STEP, $this->configuration[self::COMMERCE_CHECKOUT_STEP])
+ ->set(self::COMMERCE_GATEWAY, $payment_gateway->id())
+ ->set(self::COMMERCE_METHOD, $payment_method);
+ }
+
+ /**
+ * Get a Cart (Order) for the current user.
+ *
+ * Can be a new or existing cart.
+ *
+ * @param \Drupal\commerce_cart\CartProviderInterface $cartProvider
+ * The Cart Provider.
+ * @param \Drupal\commerce_store\Entity\StoreInterface $store
+ * The Store.
+ * @param bool $remove_existing_items
+ * Flag to remove existing items from the Cart.
+ *
+ * @return \Drupal\commerce_order\Entity\OrderInterface|null
+ * Cart of current user.
+ */
+ protected function getCart(CartProviderInterface $cartProvider, StoreInterface $store, $remove_existing_items = TRUE) {
+ $order_type = $this->configuration[self::COMMERCE_ORDER_TYPE];
+
+ /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
+ $order = $cartProvider->getCart($order_type, $store) ?: $cartProvider->createCart($order_type, $store);
+
+ if (!$order) {
+ $this->loggerFactory->get($this->pluginId)->error(t('Failed to get a Cart Order'));
+ return NULL;
+ }
+
+ if ($remove_existing_items && $order->hasItems()) {
+ foreach ($order->getItems() as $item) {
+ $order->removeItem($item);
+ }
+ }
+
+ return $order;
+ }
+
+ /**
+ * Get the selected store.
+ *
+ * @return \Drupal\commerce_store\Entity\StoreInterface
+ * The Store.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ */
+ protected function getStore() {
+ /** @var \Drupal\commerce_store\Entity\StoreInterface $store */
+ $store = $this->entityTypeManager->getStorage('commerce_store')
+ ->load($this->configuration[self::COMMERCE_STORE]);
+
+ if (!$store) {
+ $this->loggerFactory->get($this->pluginId)->error(t('Failed to get a Store'));
+ return NULL;
+ }
+
+ return $store;
+ }
+
+ /**
+ * Format the price value.
+ *
+ * We allow various field types as price input. This converts them to a float
+ * value.
+ *
+ * @param mixed $value
+ * Raw price value.
+ *
+ * @return float
+ * Converted value.
+ */
+ private function formatPrice($value) {
+ // Convert Computed Twig.
+ if ($value instanceof MarkupInterface) {
+ $value = (string) $value;
+ $value = preg_replace('/[\n\r\t]/', '', $value);
+ }
+ // Convert text.
+ $value = (string) $value;
+ $value = trim($value);
+ $value = str_replace(',', '.', str_replace('.', '', $value));
+ $value = empty($value) ? '0' : $value;
+ if (!is_numeric($value)) {
+ throw new WebformException($this->t('Can not make price from %value.', ['%value' => $value]));
+ }
+
+ return $value;
+ }
+
+}
diff --git a/src/Plugin/webform_product/WebformOptions.php b/src/Plugin/webform_product/WebformOptions.php
index 763a6fe..e0e909a 100644
--- a/src/Plugin/webform_product/WebformOptions.php
+++ b/src/Plugin/webform_product/WebformOptions.php
@@ -12,18 +12,19 @@ class WebformOptions {
public static function process(&$element, FormStateInterface $form_state) {
- // Check for price_* elements, skip the check for Option definitions.
- if (method_exists($form_state->getFormObject(), 'getElement')) {
- $element_info = $form_state->getFormObject()->getElement();
-
- // Only change the form of price_* webform elements.
- if (strpos($element_info['#type'], 'price_', 0) === FALSE) {
- return $element;
- }
- }
- else {
- return $element;
- }
+ //@todo fix this when Price* webform elements are working.
+// // Check for price_* elements, skip the check for Option definitions.
+// if (method_exists($form_state->getFormObject(), 'getElement')) {
+// $element_info = $form_state->getFormObject()->getElement();
+//
+// // Only change the form of price_* webform elements.
+// if (strpos($element_info['#type'], 'price_', 0) === FALSE) {
+// return $element;
+// }
+// }
+// else {
+// return $element;
+// }
$element['options']['#element']['price'] = [
'#type' => 'textfield',
@@ -39,6 +40,7 @@ public static function process(&$element, FormStateInterface $form_state) {
}
}
}
+
// WebFormOptions::convertValuesToOptions() destroys our values so do
// something about that.
array_unshift($element['#element_validate'], [get_class(), 'convertToSettings']);
diff --git a/src/WebFormProductFormHelper.php b/src/WebFormProductFormHelper.php
index 2e775b4..4c105c5 100644
--- a/src/WebFormProductFormHelper.php
+++ b/src/WebFormProductFormHelper.php
@@ -4,18 +4,21 @@
use Drupal\commerce_order\Entity\OrderItem;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
class WebFormProductFormHelper {
public static function processElementForm(&$element, FormStateInterface $form_state, &$complete_form) {
$element_info = $form_state->getFormObject()->getElement();
- // Only change the form of price_* webform elements.
- if (strpos($element_info['#type'], 'price_', 0) === FALSE) {
- return $element;
- }
+ //@todo fix this when Price* webform elements are working.
+// // Only change the form of price_* webform elements.
+// if (strpos($element_info['#type'], 'price_', 0) === FALSE) {
+// return $element;
+// }
$element['price'] = [
'#type' => 'textfield',
@@ -24,6 +27,7 @@ public static function processElementForm(&$element, FormStateInterface $form_st
'#maxlength' => 20,
'#default_value' => static::getSetting($form_state, 'top'),
'#element_validate' => [[get_class(), 'saveTopPrice']],
+ '#description' => t('Use this to add an extra order item to the order, this can be used as a supplement with the options or single without the prices of the options.'),
];
return $element;
}
@@ -55,60 +59,6 @@ public static function setSetting(FormStateInterface $form_state, $settingKey, $
}
}
- public static function submissionToCart(array &$form, FormStateInterface $form_state) {
- /** @var \Drupal\webform\WebformSubmissionInterface $submission */
- $submission = $form_state->getFormObject()->getEntity();
- if (!$submission instanceof WebformSubmissionInterface) {
- return;
- }
-
- $webform = $submission->getWebform();
- if (!$prices = $webform->getThirdPartySettings('webform_product')) {
- return;
- }
-
- /** @var \Drupal\commerce_store\Entity\StoreInterface $store */
- $store = \Drupal::service('commerce_store.current_store')->getStore();
- $currencyCode = $store->getDefaultCurrency()->getCurrencyCode();
- /** @var \Drupal\commerce_cart\CartProviderInterface $cartProvider */
- $cartProvider = \Drupal::service('commerce_cart.cart_provider');
- /** @var \Drupal\commerce_order\Entity\OrderInterface $cartOrder */
- $cartOrder = $cartProvider->getCart('default', $store) ?: $cartProvider->createCart('default', $store);
- $elements = $webform->getElementsInitializedAndFlattened();
- $prices = $webform->getThirdPartySettings('webform_product');
- foreach ($submission->getData() as $key => $value) {
- if (isset($prices[$key])) {
- if (!empty($prices[$key]['top'])) {
- $orderItem = OrderItem::create([
- 'type' => 'webform',
- 'title' => $elements[$key]['#title'],
- 'unit_price' => ['number' => $prices[$key]['top'], 'currency_code' => $currencyCode]
- ]);
- $orderItem->save();
- $cartOrder->addItem($orderItem);
- }
- if (!empty($prices[$key]['options'])) {
- // Fix for when value is not an array.
- if (!is_array($value)) {
- $value = [$value];
- }
-
- foreach (array_intersect($value, array_keys($prices[$key]['options'])) as $option) {
- $orderItem = OrderItem::create([
- 'type' => 'webform',
- 'title' => $elements[$key]['#options'][$option],
- 'unit_price' => ['number' => $prices[$key]['options'][$option], 'currency_code' => $currencyCode]
- ]);
- $orderItem->save();
- $cartOrder->addItem($orderItem);
- }
- }
- }
- }
- $cartOrder->save();
- $form_state->setRedirect('commerce_cart.page');
- }
-
private static function getWebformInFormState(FormStateInterface $form_state) {
/** @var \Drupal\webform_ui\Form\WebformUiElementEditForm $formObject */
$formObject = $form_state->getFormObject();
@@ -117,4 +67,5 @@ private static function getWebformInFormState(FormStateInterface $form_state) {
return ($webformObject instanceof WebformInterface) ? $webformObject : NULL;
}
+
}
diff --git a/src/WebformProductPluginManager.php b/src/WebformProductPluginManager.php
index 5162bf3..d41ab47 100644
--- a/src/WebformProductPluginManager.php
+++ b/src/WebformProductPluginManager.php
@@ -7,8 +7,16 @@
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
+/**
+ * Class WebformProductPluginManager.
+ *
+ * @package Drupal\webform_product
+ */
class WebformProductPluginManager extends DefaultPluginManager {
+ /**
+ * {@inheritdoc}
+ */
public function __construct(\Traversable $namespaces, CacheBackendInterface $cacheBackend, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', $additional_annotation_namespaces = []) {
parent::__construct('Plugin/webform_product', $namespaces, $module_handler, NULL, PluginID::class);
$this->setCacheBackend($cacheBackend, 'webform_product');
diff --git a/templates/webform-handler-webform-product-summary.html.twig b/templates/webform-handler-webform-product-summary.html.twig
new file mode 100644
index 0000000..b63c414
--- /dev/null
+++ b/templates/webform-handler-webform-product-summary.html.twig
@@ -0,0 +1,19 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of a webform action handler.
+ *
+ * Available variables:
+ * - settings: The current configuration for this debug handler.
+ * - handler: The action handler.
+ *
+ * @ingroup themeable
+ */
+#}
+
+{% if settings.store is not null %}{{ 'Store:'|t }} {{ settings.store }}
{% endif %}
+{% if settings.order_type %}{{ 'Order type:'|t }} {{ settings.order_type }}
{% endif %}
+{% if settings.checkout_step %}{{ 'Checkout step:'|t }} {{ settings.checkout_step }}
{% endif %}
+{% if settings.payment_gateway %}{{ 'Payment gateway:'|t }} {{ settings.payment_gateway }}
{% endif %}
+{% if settings.payment_method %}{{ 'Payment method:'|t }} {{ settings.payment_method }}
{% endif %}
+
diff --git a/webform_product.info.yml b/webform_product.info.yml
index 8866b9c..83c39a0 100644
--- a/webform_product.info.yml
+++ b/webform_product.info.yml
@@ -3,6 +3,8 @@ type: module
description: 'Setup a single webform as a product for Commerce.'
package: Webform
core: 8.x
+interface translation project: webform_product
+interface translation server pattern: modules/custom/%project/translations/%project.%language.po
dependencies:
- 'webform:webform'
- 'commerce:commerce'
diff --git a/webform_product.module b/webform_product.module
index 2d38622..1254387 100644
--- a/webform_product.module
+++ b/webform_product.module
@@ -1,7 +1,15 @@
$definition) {
/** @var \Drupal\Component\Plugin\PluginManagerInterface $pluginManager */
if (isset($info[$id])) {
- $info[$id]['#process'][] = [DefaultFactory::getPluginClass($id, $definition), 'process'];
+ $info[$id]['#process'][] = [
+ DefaultFactory::getPluginClass($id, $definition),
+ 'process',
+ ];
}
}
}
@@ -25,8 +36,83 @@ function webform_product_form_webform_ui_element_form_alter(&$form, FormStateInt
}
/**
- * Implements hook_webform_submission_form_alter().
+ * Implements hook_theme().
+ */
+function webform_product_theme() {
+ return [
+ 'webform_handler_webform_product_summary' => [
+ 'variables' => ['settings' => NULL, 'handler' => NULL],
+ ],
+ ];
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for commerce_checkout_flow_multistep_default.
+ *
+ * @todo Make it work for onsite payments.
*/
-function webform_product_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
- $form['actions']['submit']['#submit'][] = [WebFormProductFormHelper::class, 'submissionToCart'];
+function webform_product_form_commerce_checkout_flow_multistep_default_alter(&$form, FormStateInterface $form_state, $form_id) {
+ $offsite = NULL;
+ $payment = NULL;
+ if (isset($form['payment_process']['offsite_payment'])) {
+ $offsite = &$form['payment_process']['offsite_payment'];
+ /** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
+ $payment = $offsite['#default_value'];
+ }
+
+ if (!$offsite || !$payment) {
+ return;
+ }
+
+ $order = $payment->getOrder();
+ if (!$order || !$order->hasField(WebformProductWebformHandler::FIELD_LINK_ORDER_ORIGIN)) {
+ return;
+ }
+
+ // Load the webform submission from the saved link on the order.
+ $source_uri = $payment->getOrder()->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']);
+
+ $options = [
+ 'query' => [
+ 'submission' => $webformSubmission->getToken(),
+ ],
+ 'absolute' => TRUE,
+ ];
+
+ $webform_id = $webformSubmission->getWebform()->id();
+
+ $offsite['#return_url'] = Url::fromRoute('webform_product.payment.completed', ['webform' => $webform_id, 'order' => $order->id()], $options)->toString();
+ $offsite['#cancel_url'] = Url::fromRoute('webform_product.payment.canceled', ['webform' => $webform_id, 'order' => $order->id()], $options)->toString();
+ $offsite['#exception_url'] = Url::fromRoute('webform_product.payment.exception', ['webform' => $webform_id, 'order' => $order->id()], $options)->toString();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for webform_settings_confirmation_form.
+ *
+ * Remove not implemented confirmation types,
+ * if the webform_product handler is used.
+ */
+function webform_product_form_webform_settings_confirmation_form_alter(&$form, FormStateInterface $form_state) {
+ /** @var \Drupal\webform\WebformInterface $webform */
+ $webform = $form_state->getFormObject()->getEntity();
+
+ $handlers = $webform->getHandlers('webform_product', TRUE);
+ if ($handlers->count() == 0) {
+ return;
+ }
+
+ foreach ($form['confirmation_type']['confirmation_type']['#options'] as $key => $option) {
+ switch ($key) {
+ case WebformInterface::CONFIRMATION_URL:
+ case WebformInterface::CONFIRMATION_URL_MESSAGE:
+ break;
+
+ default:
+ unset($form['confirmation_type']['confirmation_type']['#options'][$key]);
+ }
+ }
}
diff --git a/webform_product.routing.yml b/webform_product.routing.yml
new file mode 100644
index 0000000..80cdc57
--- /dev/null
+++ b/webform_product.routing.yml
@@ -0,0 +1,20 @@
+webform_product.payment.completed:
+ path: '/webform-product/{webform}/{order}/completed'
+ defaults:
+ _controller: '\Drupal\webform_product\Controller\WebformProductController::completedSubmission'
+ requirements:
+ _access: 'TRUE'
+
+webform_product.payment.canceled:
+ path: '/webform-product/{webform}/{order}/canceled'
+ defaults:
+ _controller: '\Drupal\webform_product\Controller\WebformProductController::canceledSubmission'
+ requirements:
+ _access: 'TRUE'
+
+webform_product.payment.exception:
+ path: '/webform-product/{webform}/{order}/exception'
+ defaults:
+ _controller: '\Drupal\webform_product\Controller\WebformProductController::exceptionSubmission'
+ requirements:
+ _access: 'TRUE'
diff --git a/webform_product.services.yml b/webform_product.services.yml
index b9742d8..b90b414 100644
--- a/webform_product.services.yml
+++ b/webform_product.services.yml
@@ -2,4 +2,7 @@ services:
plugin.manager.webform_product:
class: Drupal\webform_product\WebformProductPluginManager
parent: default_plugin_manager
-
+ webform_product.order_subscriber:
+ class: Drupal\webform_product\EventSubscriber\OrderEventSubscriber
+ tags:
+ - { name: event_subscriber }