PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / payment-gateways / gateway-cod.php

gateway-cod.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/payment-gateways/gateway-cod.php

294 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Gateway COD.
4 */
5
6 namespace StoreEngine\Payment\Gateways;
7
8 use StoreEngine\Admin\Notices;
9 use StoreEngine\Classes\Exceptions\StoreEngineException;
10 use StoreEngine\Classes\Exceptions\StoreEngineInvalidOrderStatusException;
11 use StoreEngine\Classes\Exceptions\StoreEngineInvalidOrderStatusTransitionException;
12 use StoreEngine\Classes\Order;
13 use StoreEngine\Classes\OrderContext;
14 use StoreEngine\Shipping\Shipping;
15 use StoreEngine\Shipping\ShippingZone;
16 use StoreEngine\Shipping\ShippingZones;
17 use StoreEngine\Utils\Formatting;
18 use StoreEngine\Utils\Helper;
19 use StoreEngine\Utils\ShippingUtils;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit; // Exit if accessed directly.
23 }
24
25 class GatewayCod extends PaymentGateway {
26
27 public string $instructions;
28 public bool $enable_for_virtual;
29
30 public ?array $enable_for_methods;
31
32 public function __construct() {
33 $this->setup();
34
35 $this->init_admin_fields();
36 $this->init_settings();
37
38 $this->title = $this->get_option( 'title' );
39 $this->description = $this->get_option( 'description' );
40 $this->instructions = $this->get_option( 'instructions' );
41 $this->enable_for_virtual = $this->get_option( 'enable_for_virtual', false );
42 $this->enable_for_methods = $this->get_option( 'enable_for_methods', [] );
43
44 add_action( 'storeengine/thankyou/' . $this->id, [ $this, 'thankyou_page' ] );
45 add_action( 'storeengine/email_before_order_table', [ $this, 'email_instructions' ], 10, 3 );
46
47 if ( $this->is_enabled() && ! $this->enable_for_virtual && ! ShippingUtils::get_shipping_methods_count() ) {
48 Notices::add_notice( 'no-shipping-zone-methods', [
49 'type' => 'warning',
50 'dismissible' => false,
51 'message' => sprintf(
52 // translators: %s: link to addon page.
53 __( 'The <b>Cash on Delivery</b> payment method is available only if at least one <a href="%s">Shipping Method</a> is configured, or if the <code>Accept for virtual orders</code> option is enabled.', 'storeengine' ),
54 esc_url( admin_url( 'admin.php?page=storeengine-settings&path=shipping' ) )
55 ),
56 ] );
57 }
58 }
59
60 protected function setup() {
61 $this->id = 'cod';
62 $this->icon = apply_filters( 'storeengine/cod_icon', Helper::get_assets_url( 'images/payment-methods/cod-alt.svg' ) );
63 $this->method_title = __( 'Cash on delivery', 'storeengine' );
64 $this->method_description = __( 'Let your shoppers pay upon delivery — by cash or other methods of payment.', 'storeengine' );
65 $this->has_fields = false;
66 }
67
68 public function is_available(): bool {
69 $is_virtual = true;
70 $shipping_methods = [];
71
72 if ( Formatting::string_to_bool( get_query_var( 'order_pay' ) ) ) {
73 $order = Helper::get_order( absint( get_query_var( 'order_id' ) ) );
74
75 if ( ! is_wp_error( $order ) ) {
76 $shipping_methods = $order->get_shipping_methods();
77 $is_virtual = $order->get_auto_complete_digital_order() || ! count( $order->get_shipping_methods() );
78 }
79 } elseif ( \StoreEngine::init()->get_cart() && \StoreEngine::init()->get_cart()->needs_shipping() ) {
80 $shipping_methods = \StoreEngine::init()->get_cart()->get_shipping_methods();
81 $is_virtual = false;
82 }
83
84 // If COD is not enabled for virtual orders and the order does not need shipping, return false.
85 if ( ! $this->enable_for_virtual && $is_virtual ) {
86 return false;
87 }
88
89 // Return early if:
90 // - There are no shipping methods restrictions in place.
91 // - The order is virtual so needs no shipping.
92 // - Shipping methods are not set yet.
93 if ( empty( $this->enable_for_methods ) || $is_virtual || ! $shipping_methods ) {
94 return parent::is_available();
95 }
96
97 // Get the selected shipping method ids. This works on both ShippingRate and OrderItemShipping class instances.
98 $canonical_rate_ids = array_unique(
99 array_values(
100 array_filter(
101 array_map(
102 function ( $shipping_method ) {
103 return $shipping_method
104 && is_callable( array( $shipping_method, 'get_method_id' ) )
105 && is_callable( array( $shipping_method, 'get_instance_id' ) )
106 ? $shipping_method->get_method_id() . ':' . $shipping_method->get_instance_id()
107 : null;
108 },
109 $shipping_methods
110 )
111 )
112 )
113 );
114
115 if ( ! count( $this->get_matching_rates( $canonical_rate_ids ) ) ) {
116 return false;
117 }
118
119 return parent::is_available();
120 }
121
122 /**
123 * Indicates whether a rate exists in an array of canonically-formatted rate IDs that activates this gateway.
124 *
125 * @since 1.6.4
126 *
127 * @param array $rate_ids Rate ids to check.
128 * @return array
129 */
130 private function get_matching_rates( array $rate_ids ): array {
131 // First, match entries in 'method_id:instance_id' format. Then, match entries in 'method_id' format by stripping off the instance ID from the candidates.
132 return array_unique(
133 array_merge(
134 array_intersect( $this->enable_for_methods, $rate_ids ),
135 array_intersect(
136 $this->enable_for_methods,
137 array_unique( array_map( [ Formatting::class, 'get_string_before_colon' ], $rate_ids ) )
138 )
139 )
140 );
141 }
142
143 /**
144 * Process the payment and return the result.
145 *
146 * @param Order $order
147 *
148 * @return array
149 * @throws StoreEngineInvalidOrderStatusException
150 * @throws StoreEngineInvalidOrderStatusTransitionException
151 * @throws StoreEngineException
152 */
153
154 public function process_payment( Order $order ): array {
155 try {
156 if ( $this->is_payment_needed( $order ) ) {
157 $order->add_order_note( _x( 'Payment to be made upon delivery.', 'COD payment method', 'storeengine' ) );
158 $order->set_paid_status( 'unpaid' );
159 } else {
160 $order->set_paid_status( 'paid' );
161 $order->add_order_note( _x( 'Payment not needed.', 'COD payment method', 'storeengine' ) );
162 }
163
164 $order_context = new OrderContext( $order->get_status() );
165 $order_context->proceed_to_next_status( 'processing', $order );
166 $order->save();
167
168 Helper::cart()->clear_cart();
169
170 \StoreEngine\Classes\Logger::log( 'COD Payment Success', [ 'order_id' => $order->get_id() ], 'success', 'cod' );
171
172 return [
173 'result' => 'success',
174 'redirect' => $this->get_return_url( $order ),
175 ];
176
177 } catch ( \Exception $e ) {
178 \StoreEngine\Classes\Logger::log( 'COD Payment Failed', $e->getMessage(), 'error', 'cod' );
179 return[
180 'result' => 'failure',
181 'message' => $e->getMessage()
182 ];
183 }
184 }
185
186 protected function init_admin_fields() {
187 $this->admin_fields = [
188 'title' => [
189 'label' => __( 'Title', 'storeengine' ),
190 'type' => 'safe_text',
191 'tooltip' => __( 'This controls the title which the user sees during checkout.', 'storeengine' ),
192 'default' => __( 'Cash on delivery', 'storeengine' ),
193 'priority' => 0,
194 ],
195 'description' => [
196 'label' => __( 'Description', 'storeengine' ),
197 'type' => 'textarea',
198 'tooltip' => __( 'Payment method description that the customer will see on your checkout.', 'storeengine' ),
199 'default' => __( 'Pay with cash upon delivery.', 'storeengine' ),
200 'priority' => 0,
201 ],
202 'instructions' => [
203 'label' => __( 'Instructions', 'storeengine' ),
204 'type' => 'textarea',
205 'tooltip' => __( 'Instructions that will be added to the thank you page and emails.', 'storeengine' ),
206 'default' => __( 'Pay with cash upon delivery.', 'storeengine' ),
207 'priority' => 0,
208 ],
209 'enable_for_methods' => [
210 'label' => __( 'Enable for shipping methods', 'storeengine' ),
211 'placeholder' => __( 'Select shipping methods', 'storeengine' ),
212 'type' => 'multiselect',
213 'multi' => true,
214 'default' => [],
215 'tooltip' => __( 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', 'storeengine' ),
216 'options' => $this->get_shipping_method_options(),
217 ],
218 'enable_for_virtual' => [
219 'label' => __( 'Accept for virtual orders', 'storeengine' ),
220 'tooltip' => __( 'Accept COD if the order is virtual.', 'storeengine' ),
221 'type' => 'checkbox',
222 'default' => true,
223 ],
224 ];
225 }
226
227 private function get_shipping_method_options(): array {
228 try {
229 $raw_zones = ShippingZones::get_zones();
230 $zones = [];
231
232 foreach ( $raw_zones as $raw_zone ) {
233 $zones[] = new ShippingZone( $raw_zone['zone_id'] );
234 }
235
236 $zones[] = new ShippingZone( 0 );
237
238 $options = [];
239
240 foreach ( Shipping::init()->load_shipping_methods() as $method ) {
241
242 $options[ $method->get_method_id() ] = [
243 'label' => $method->get_method_title(),
244 'options' => [],
245 ];
246
247 $options[ $method->get_method_id() ]['options'][] = [
248 // Translators: %1$s. Shipping method name.
249 'label' => sprintf( __( 'Any "%1$s" method', 'storeengine' ), $method->get_method_title() ),
250 'value' => $method->get_method_id(),
251 ];
252
253 foreach ( $zones as $zone ) {
254
255 $shipping_method_instances = $zone->get_shipping_methods();
256
257 foreach ( $shipping_method_instances as $shipping_method_instance_id => $shipping_method_instance ) {
258
259 if ( $shipping_method_instance->get_method_id() !== $method->get_method_id() ) {
260 continue;
261 }
262
263 $option_instance_title = sprintf(
264 // Translators: %1$s shipping method title, %2$s shipping method id.
265 _x( '%1$s (#%2$s)', 'Shipping method & instance ID (Dropdown option label formatting).', 'storeengine' ),
266 $shipping_method_instance->get_name(),
267 $shipping_method_instance_id
268 );
269
270 $option_title = sprintf(
271 // Translators: %1$s zone name, %2$s shipping method instance name.
272 _x( '%1$s – %2$s', 'Shipping zone & shipping method name (Dropdown option label formatting).', 'storeengine' ),
273 $zone->get_id() ? $zone->get_zone_name() : __( 'Other locations', 'storeengine' ),
274 $option_instance_title
275 );
276
277 $options[ $method->get_method_id() ]['options'][] = [
278 'label' => $option_title,
279 'value' => $shipping_method_instance->get_rate_id(),
280 ];
281 }
282 }
283 }
284
285 // React-select only allow array.
286 return array_values( $options );
287 } catch (\Exception $e) {}
288
289 return [];
290 }
291 }
292
293 // End of file gateway-cod.php.
294