Skip to content

Commit 5d194b0

Browse files
committed
Issue #2858599 by bojanz: Move a portion of the PaymentInformation pane logic to a PaymentOptionsBuilder
1 parent 056f69d commit 5d194b0

7 files changed

Lines changed: 727 additions & 210 deletions

File tree

modules/payment/commerce_payment.services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ services:
1515
plugin.manager.commerce_payment_type:
1616
class: Drupal\commerce_payment\PaymentTypeManager
1717
parent: default_plugin_manager
18+
19+
commerce_payment.options_builder:
20+
class: Drupal\commerce_payment\PaymentOptionsBuilder
21+
arguments: ['@entity_type.manager']
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Drupal\commerce_payment;
4+
5+
/**
6+
* Represents a payment option.
7+
*
8+
* @see \Drupal\commerce_payment\PaymentOptionsBuilderInterface::buildOptions()
9+
*/
10+
final class PaymentOption {
11+
12+
/**
13+
* The ID.
14+
*
15+
* @var string
16+
*/
17+
protected $id;
18+
19+
/**
20+
* The label.
21+
*
22+
* @var string
23+
*/
24+
protected $label;
25+
26+
/**
27+
* The payment gateway ID.
28+
*
29+
* @var string
30+
*/
31+
protected $paymentGatewayId;
32+
33+
/**
34+
* The payment method ID, when known.
35+
*
36+
* @var string
37+
*/
38+
protected $paymentMethodId;
39+
40+
/**
41+
* The payment method type ID, when known.
42+
*
43+
* @var string
44+
*/
45+
protected $paymentMethodTypeId;
46+
47+
/**
48+
* Constructs a new PaymentOption object.
49+
*
50+
* @param array $definition
51+
* The definition.
52+
*/
53+
public function __construct(array $definition) {
54+
foreach (['id', 'label', 'payment_gateway_id'] as $required_property) {
55+
if (empty($definition[$required_property])) {
56+
throw new \InvalidArgumentException(sprintf('Missing required property "%s".', $required_property));
57+
}
58+
}
59+
60+
$this->id = $definition['id'];
61+
$this->label = $definition['label'];
62+
$this->paymentGatewayId = $definition['payment_gateway_id'];
63+
if (isset($definition['payment_method_id'])) {
64+
$this->paymentMethodId = $definition['payment_method_id'];
65+
}
66+
if (isset($definition['payment_method_type_id'])) {
67+
$this->paymentMethodTypeId = $definition['payment_method_type_id'];
68+
}
69+
}
70+
71+
/**
72+
* Gets the ID.
73+
*
74+
* @return string
75+
* The ID.
76+
*/
77+
public function getId() {
78+
return $this->id;
79+
}
80+
81+
/**
82+
* Gets the label.
83+
*
84+
* @return string
85+
* The label.
86+
*/
87+
public function getLabel() {
88+
return $this->label;
89+
}
90+
91+
/**
92+
* Gets the payment gateway ID.
93+
*
94+
* @return string
95+
* The payment gateway ID.
96+
*/
97+
public function getPaymentGatewayId() {
98+
return $this->paymentGatewayId;
99+
}
100+
101+
/**
102+
* Gets the payment method ID.
103+
*
104+
* Only available when selecting existing payment methods.
105+
*
106+
* @return string|null
107+
* The payment method ID, or NULL if not known.
108+
*/
109+
public function getPaymentMethodId() {
110+
return $this->paymentMethodId;
111+
}
112+
113+
/**
114+
* Gets the payment method type ID.
115+
*
116+
* Only available when adding payment methods.
117+
*
118+
* @return string|null
119+
* The payment method type ID, or NULL if not known.
120+
*/
121+
public function getPaymentMethodTypeId() {
122+
return $this->paymentMethodTypeId;
123+
}
124+
125+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
namespace Drupal\commerce_payment;
4+
5+
use Drupal\commerce\EntityHelper;
6+
use Drupal\commerce_order\Entity\OrderInterface;
7+
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
8+
use Drupal\Core\Entity\EntityTypeManagerInterface;
9+
10+
class PaymentOptionsBuilder implements PaymentOptionsBuilderInterface {
11+
12+
/**
13+
* The entity type manager.
14+
*
15+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
16+
*/
17+
protected $entityTypeManager;
18+
19+
/**
20+
* Constructs a new PaymentOptionsBuilder object.
21+
*
22+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
23+
* The entity type manager.
24+
*/
25+
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
26+
$this->entityTypeManager = $entity_type_manager;
27+
}
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function buildOptions(OrderInterface $order, array $payment_gateways = []) {
33+
if (empty($payment_gateways)) {
34+
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
35+
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
36+
$payment_gateways = $payment_gateway_storage->loadMultipleForOrder($order);
37+
}
38+
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways_with_payment_methods */
39+
$payment_gateways_with_payment_methods = array_filter($payment_gateways, function ($payment_gateway) {
40+
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
41+
return $payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface;
42+
});
43+
44+
$options = [];
45+
// 1) Add options to reuse stored payment methods for known customers.
46+
$customer = $order->getCustomer();
47+
if ($customer) {
48+
$billing_countries = $order->getStore()->getBillingCountries();
49+
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
50+
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
51+
52+
foreach ($payment_gateways_with_payment_methods as $payment_gateway) {
53+
$payment_methods = $payment_method_storage->loadReusable($customer, $payment_gateway, $billing_countries);
54+
55+
foreach ($payment_methods as $payment_method_id => $payment_method) {
56+
$options[$payment_method_id] = new PaymentOption([
57+
'id' => $payment_method_id,
58+
'label' => $payment_method->label(),
59+
'payment_gateway_id' => $payment_gateway->id(),
60+
'payment_method_id' => $payment_method_id,
61+
]);
62+
}
63+
}
64+
}
65+
66+
// 2) Add the order's payment method if it was not included above.
67+
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $order_payment_method */
68+
$order_payment_method = $order->get('payment_method')->entity;
69+
if ($order_payment_method) {
70+
$order_payment_method_id = $order_payment_method->id();
71+
// Make sure that the payment method's gateway is still available.
72+
$payment_gateway_id = $order_payment_method->getPaymentGatewayId();
73+
$payment_gateway_ids = EntityHelper::extractIds($payment_gateways_with_payment_methods);
74+
75+
if (in_array($payment_gateway_id, $payment_gateway_ids) && !isset($options[$order_payment_method_id])) {
76+
$options[$order_payment_method_id] = new PaymentOption([
77+
'id' => $order_payment_method_id,
78+
'label' => $order_payment_method->label(),
79+
'payment_gateway_id' => $order_payment_method->getPaymentGatewayId(),
80+
'payment_method_id' => $order_payment_method_id,
81+
]);
82+
}
83+
}
84+
85+
// 3) Add options to create new stored payment methods of supported types.
86+
$payment_method_type_counts = [];
87+
// Count how many new payment method options will be built per gateway.
88+
foreach ($payment_gateways_with_payment_methods as $payment_gateway) {
89+
$payment_method_types = $payment_gateway->getPlugin()->getPaymentMethodTypes();
90+
91+
foreach ($payment_method_types as $payment_method_type_id => $payment_method_type) {
92+
if (!isset($payment_method_type_counts[$payment_method_type_id])) {
93+
$payment_method_type_counts[$payment_method_type_id] = 1;
94+
}
95+
else {
96+
$payment_method_type_counts[$payment_method_type_id]++;
97+
}
98+
}
99+
}
100+
101+
foreach ($payment_gateways_with_payment_methods as $payment_gateway) {
102+
$payment_gateway_plugin = $payment_gateway->getPlugin();
103+
$payment_method_types = $payment_gateway_plugin->getPaymentMethodTypes();
104+
105+
foreach ($payment_method_types as $payment_method_type_id => $payment_method_type) {
106+
$option_id = 'new--' . $payment_method_type_id . '--' . $payment_gateway->id();
107+
$option_label = $payment_method_type->getCreateLabel();
108+
// If there is more than one option for this payment method type,
109+
// append the payment gateway label to avoid duplicate option labels.
110+
if ($payment_method_type_counts[$payment_method_type_id] > 1) {
111+
$option_label = $this->t('@payment_method_label (@payment_gateway_label)', [
112+
'@payment_method_label' => $payment_method_type->getCreateLabel(),
113+
'@payment_gateway_label' => $payment_gateway_plugin->getDisplayLabel(),
114+
]);
115+
}
116+
117+
$options[$option_id] = new PaymentOption([
118+
'id' => $option_id,
119+
'label' => $option_label,
120+
'payment_gateway_id' => $payment_gateway->id(),
121+
'payment_method_type_id' => $payment_method_type_id,
122+
]);
123+
}
124+
}
125+
126+
// 4) Add options for the remaining gateways (off-site, manual, etc).
127+
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $other_payment_gateways */
128+
$other_payment_gateways = array_diff_key($payment_gateways, $payment_gateways_with_payment_methods);
129+
foreach ($other_payment_gateways as $payment_gateway) {
130+
$payment_gateway_id = $payment_gateway->id();
131+
$options[$payment_gateway_id] = new PaymentOption([
132+
'id' => $payment_gateway_id,
133+
'label' => $payment_gateway->getPlugin()->getDisplayLabel(),
134+
'payment_gateway_id' => $payment_gateway_id,
135+
]);
136+
}
137+
138+
return $options;
139+
}
140+
141+
/**
142+
* {@inheritdoc}
143+
*/
144+
public function selectDefaultOption(OrderInterface $order, array $options) {
145+
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $order_payment_gateway */
146+
$order_payment_gateway = $order->get('payment_gateway')->entity;
147+
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $order_payment_method */
148+
$order_payment_method = $order->get('payment_method')->entity;
149+
150+
$default_option_id = NULL;
151+
if ($order_payment_method) {
152+
$default_option_id = $order_payment_method->id();
153+
}
154+
elseif ($order_payment_gateway && !($order_payment_gateway instanceof SupportsStoredPaymentMethodsInterface)) {
155+
$default_option_id = $order_payment_gateway->id();
156+
}
157+
// The order doesn't have a payment method/gateway specified, or it has, but it is no longer available.
158+
if (!$default_option_id || !isset($options[$default_option_id])) {
159+
$option_ids = array_keys($options);
160+
$default_option_id = reset($option_ids);
161+
}
162+
163+
return $options[$default_option_id];
164+
}
165+
166+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Drupal\commerce_payment;
4+
5+
use Drupal\commerce_order\Entity\OrderInterface;
6+
7+
/**
8+
* Builds payment options for an order.
9+
*/
10+
interface PaymentOptionsBuilderInterface {
11+
12+
/**
13+
* Builds the payment options for the given order's payment gateways.
14+
*
15+
* The payment options will be derived from the given payment gateways
16+
* in the following order:
17+
* 1) The customer's stored payment methods.
18+
* 2) The order's payment method (if not added in the previous step).
19+
* 3) Options to create new payment methods of valid types.
20+
* 4) Options for the remaining gateways (off-site, manual, etc).
21+
*
22+
* @param \Drupal\commerce_order\Entity\OrderInterface $order
23+
* The order.
24+
* @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways
25+
* The payment gateways. When empty, defaults to all available gateways.
26+
*
27+
* @return \Drupal\commerce_payment\PaymentOption[]
28+
* The payment options, keyed by option ID.
29+
*/
30+
public function buildOptions(OrderInterface $order, array $payment_gateways = []);
31+
32+
/**
33+
* Selects the default payment option for the given order.
34+
*
35+
* Priority:
36+
* 1) The order's payment method
37+
* 2) The order's payment gateway (if it does not support payment methods)
38+
* 3) First defined option.
39+
*
40+
* @param \Drupal\commerce_order\Entity\OrderInterface $order
41+
* The order.
42+
* @param array $options
43+
* The options.
44+
*
45+
* @return \Drupal\commerce_payment\PaymentOption
46+
* The selected option.
47+
*/
48+
public function selectDefaultOption(OrderInterface $order, array $options);
49+
50+
}

0 commit comments

Comments
 (0)