PluginProbe
MONEI Payments for WooCommerce / 7.2.2
MONEI Payments for WooCommerce v7.2.2
7.3.3 7.3.2 7.3.1 7.3.0 7.2.4 7.2.3 7.2.2 7.2.0 7.2.1 7.1.3 2.1.0 3.0.0 3.1.0 3.1.1 4.0.0 4.1.0 4.1.1 4.2.0 4.2.1 5.0 5.1.0 5.1.1 5.1.2 5.2.2 5.2.3 All 87 releases
monei / src / Features / Subscriptions / WooCommerceSubscriptionsHandler.php

WooCommerceSubscriptionsHandler.php in MONEI Payments for WooCommerce 7.2.2, at src/Features/Subscriptions/WooCommerceSubscriptionsHandler.php

326 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Monei\Features\Subscriptions;
4
5 use Monei\Services\payment\MoneiPaymentServices;
6 use Monei\Services\sdk\MoneiSdkClientFactory;
7 use Monei\Services\ApiKeyService;
8 use Exception;
9 use WC_Monei_Logger;
10 use WC_Order;
11 use WC_Subscriptions_Product;
12
13 class WooCommerceSubscriptionsHandler implements SubscriptionHandlerInterface {
14
15 private $moneiPaymentServices;
16
17 public function __construct( MoneiSdkClientFactory $sdkClient ) {
18 $this->moneiPaymentServices = new MoneiPaymentServices( $sdkClient );
19 }
20
21 /**
22 * Checks if subscription plugin is enabled.
23 *
24 * @since 5.0
25 *
26 * @return bool
27 */
28 public function is_subscriptions_addon_enabled(): bool {
29 return class_exists( 'WC_Subscriptions' );
30 }
31
32 public function is_subscription_order( int $order_id ): bool {
33 if ( ! $this->is_subscriptions_addon_enabled() ) {
34 return false;
35 }
36 return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription(
37 $order_id
38 ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
39 }
40
41 /**
42 * Checks if page is pay for order and change subs payment page.
43 *
44 * @return bool
45 */
46 public function is_subscription_change_payment_page(): bool {
47 return (isset($_GET['pay_for_order']) && isset($_GET['change_payment_method'])); // phpcs:ignore
48 }
49
50 public function get_subscriptions_for_order( int $order_id ): array {
51 // new WC_Subscription( $order_id );
52 return wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => array( 'any' ) ) );
53 }
54
55 /**
56 * If Subscription has a free trial, first payment 0 euros.
57 * We will charge customer 1 cent ( create_subscription_payload ):
58 * 1. Free Trial
59 * 2. Payment made with tokenized card ( paymentToken is set )
60 *
61 * Therefore, after the 1 cent payment, we need to refund it automatically.
62 * This hooks will trigger after successful (1 cent) payment.
63 *
64 * @param $confirm_payload
65 * @param $confirm_payment
66 * @param $order
67 *
68 * @throws \Monei\ApiException
69 */
70 public function subscription_after_payment_success( $confirm_payload, $confirm_payment, $order ): void {
71 /** If order is not subscription, bail. */
72 if ( ! $this->is_subscription_order( $order->get_id() ) ) {
73 return;
74 }
75
76 /** If payment wasn't 1 cent, bail. */
77 if ( 1 !== $confirm_payload['amount'] ) {
78 return;
79 }
80
81 /** If payment is not done with a tokenized card, bail. */
82 if ( ! isset( $confirm_payload['paymentToken'] ) ) {
83 return;
84 }
85
86 /** Refund that cent. */
87 $this->moneiPaymentServices->refund_payment( $confirm_payment->getId(), 1 );
88 }
89
90 /**
91 * It adds subscription configuration to the payload.
92 *
93 * @param $order
94 * @param $payment_method
95 *
96 * @return array
97 */
98 public function create_subscription_payload( WC_Order $order, $payment_method, $payload ): array {
99 $payload['sequence'] = array(
100 'type' => 'recurring',
101 'recurring' => array(
102 'frequency' => 1, // Testing with 1 to know if we can modify subscription dates.
103 ),
104 );
105
106 /**
107 * If there is a free trial, (first payment for free) and user has selected a tokenized card,
108 * We hit a monei limitation, so we need to charge the customer 1 cent, that will be refunded afterwards.
109 */
110 if ( 0 === monei_price_format( $order->get_total() ) && isset( $payload['paymentToken'] ) ) {
111 $payload['amount'] = 1;
112 }
113
114 /**
115 * Supporting Subscriber Payment Method Changes
116 * https://docs.woocommerce.com/document/subscriptions/develop/payment-gateway-integration/#section-18
117 *
118 * We need to charge 0, in order to get new sequence id.
119 * If customer has selected a tokenized card, because of monei restrictions
120 * we need to charge one cent, to be refunded afterwards in order to get new sequence_id.
121 */
122 if ( $this->is_subscription_change_payment_page() ) {
123 $payload['amount'] = 0;
124 if ( isset( $payload['paymentToken'] ) ) {
125 $payload['amount'] = 1;
126 }
127
128 $payload['orderId'] = $order->get_id() . '_verification' . time();
129 $payload['description'] = $payload['description'] . ' ' . __( 'Payment Method Subscription Change', 'monei' );
130 }
131
132 $payload = apply_filters( 'wc_gateway_monei_create_subscription_payload', $payload );
133 return $payload;
134 }
135
136 public function add_extra_info_to_subscriptions_payment_method_title( $payment_method_to_display, $subscription ): string {
137 // We only will modify Monei subscriptions titles.
138 if ( $subscription->get_payment_method() !== $this->id ) {
139 return $payment_method_to_display;
140 }
141 return $payment_method_to_display . ' - ' . $this->get_subscription_payment_method_friendly_name( $subscription );
142 }
143
144 /**
145 * Process payment on renewal. Woo automatically triggers this hooks once subscription needs to be renewed.
146 *
147 * @param $amount_to_charge
148 * @param WC_Order $renewal_order
149 */
150 public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ): void {
151 $sequence_id = $this->get_sequence_id_from_renewal_order( $renewal_order );
152 $description = get_bloginfo( 'name' ) . ' - #' . (string) $renewal_order->get_id() . ' - Subscription Renewal';
153
154 $payload = array(
155 'orderId' => (string) $renewal_order->get_id(),
156 'amount' => monei_price_format( $amount_to_charge ),
157 'description' => $description,
158 );
159
160 try {
161 $payment = $this->moneiPaymentServices->recurring_payment( $sequence_id, $payload );
162
163 if ( 'SUCCEEDED' === $payment->getStatus() ) {
164 $renewal_order->payment_complete( $payment->getId() );
165
166 $order_note = __( 'Success Renewal scheduled_subscription_payment.', 'monei' ) . '<br>';
167 $order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '. <br><br>';
168 $order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
169 $renewal_order->add_order_note( $order_note );
170
171 do_action( 'wc_gateway_monei_scheduled_subscription_payment_success', $renewal_order, $amount_to_charge );
172 } else {
173 $order_note = __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . '<br>';
174 $order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '. <br><br>';
175 $order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
176 $renewal_order->update_status( 'failed' );
177 $renewal_order->add_order_note( $order_note );
178
179 do_action( 'wc_gateway_monei_scheduled_subscription_payment_not_succeeded', $renewal_order, $amount_to_charge );
180 }
181 $renewal_order->save();
182 } catch ( Exception $e ) {
183 do_action( 'wc_gateway_monei_scheduled_subscription_payment_error', $e, $renewal_order, $amount_to_charge );
184 WC_Monei_Logger::logError( $e );
185 $renewal_order->update_status( 'failed' );
186 $renewal_order->add_order_note( __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . $e->getMessage() );
187 $renewal_order->save();
188 if ( isset( $_REQUEST['process_early_renewal'] ) && ! wp_doing_cron() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
189 wc_add_notice( $e->getMessage(), 'error' );
190 }
191 }
192 }
193
194 public function update_subscription_meta_data( $subscriptions, $payment ): void {
195 /** Iterate all subscriptions contained in the order, and add sequence id and cc data individually. */
196 foreach ( $subscriptions as $subscription_id => $subscription ) {
197 $subscription->update_meta_data( '_monei_sequence_id', $payment->getSequenceId() );
198 $subscription->update_meta_data( '_monei_payment_method_brand', $payment->getPaymentMethod()->getCard()->getBrand() );
199 $subscription->update_meta_data( '_monei_payment_method_4_last_digits', $payment->getPaymentMethod()->getCard()->getLast4() );
200 $subscription->save_meta_data();
201 }
202 }
203
204 public function init_subscriptions( array $supports, string $gateway_id ): array {
205 add_action( 'wc_gateway_monei_create_payment_success', array( $this, 'subscription_after_payment_success' ), 1, 3 );
206 add_action( 'woocommerce_scheduled_subscription_payment_' . $gateway_id, array( $this, 'scheduled_subscription_payment' ), 1, 2 );
207
208 // Add Payment information to Payment method name in "Subscription" Tab.
209 add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'add_extra_info_to_subscriptions_payment_method_title' ), 10, 2 );
210 return array_merge(
211 $supports,
212 array(
213 'subscriptions',
214 'subscription_cancellation',
215 'subscription_suspension',
216 'subscription_reactivation',
217 'subscription_amount_changes',
218 'subscription_date_changes',
219 'subscription_payment_method_change',
220 'subscription_payment_method_change_customer',
221 'multiple_subscriptions',
222 )
223 );
224 }
225
226 public function get_cart_subscription_interval_in_days() {
227 if ( WC()->cart === null ) {
228 return 0;
229 }
230 foreach ( WC()->cart->cart_contents as $cart_item ) {
231 if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
232 $interval = WC_Subscriptions_Product::get_interval( $cart_item['data'] );
233 $period = WC_Subscriptions_Product::get_period( $cart_item['data'] );
234 break;
235 }
236 }
237
238 switch ( $period ) {
239 case 'year':
240 $interval_in_days = $interval * 365;
241 break;
242 case 'month':
243 $interval_in_days = $interval * 28; // Monei Needs minimun days, to be safe, 28.
244 break;
245 case 'week':
246 $interval_in_days = $interval * 7;
247 break;
248 default:
249 $interval_in_days = $interval;
250 break;
251 }
252
253 return $interval_in_days;
254 }
255
256 /**
257 * Retrieves parent order id from a renewal order.
258 *
259 * @param $renewal_order
260 *
261 * @return false|WC_Order
262 */
263 public function get_parent_for_renewal_order_id( $renewal_order ) {
264 $subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order );
265 $subscription = array_pop( $subscriptions );
266
267 if ( 0 === $subscription->get_parent_id() ) {
268 $parent_order = null;
269 } else {
270 $parent_order = $subscription->get_parent();
271 }
272 return $parent_order;
273 }
274
275 /**
276 * Retrieves parent order from a subscription order.
277 *
278 * @param \WCS_Subscription $subscription_order
279 *
280 * @return \WC_Order|false
281 */
282 public function get_parent_for_subscription_id( $subscription_order ) {
283 return $subscription_order->get_parent();
284 }
285
286 /**
287 * From renewal order, get monei sequence id.
288 *
289 * @param $renewal_order
290 *
291 * @return string|false
292 */
293 public function get_sequence_id_from_renewal_order( $renewal_order ) {
294 return $renewal_order->get_meta( '_monei_sequence_id', true );
295 }
296
297 /**
298 * Gets a readable string to present in subscription frontend.
299 *
300 * @param $subscription
301 *
302 * @return string
303 */
304 public function get_subscription_payment_method_friendly_name( $subscription ) {
305 $brand = $subscription->get_meta( '_monei_payment_method_brand', true );
306 $last_digits = $subscription->get_meta( '_monei_payment_method_4_last_digits', true );
307 /* translators: 1) card brand 2) last 4 digits */
308 return sprintf( __( '%1$s card ending in %2$s', 'monei' ), $brand, $last_digits );
309 }
310
311 /**
312 * Check if a product is a subscription using WooCommerce Subscription logic
313 *
314 * @return bool
315 */
316 public function cart_has_subscription() {
317 if ( ! $this->is_subscriptions_addon_enabled() ) {
318 return false;
319 }
320 if ( WC()->cart === null ) {
321 return false;
322 }
323 return is_array( WC()->cart->recurring_carts ) && count( WC()->cart->recurring_carts ) > 0;
324 }
325 }
326