PluginProbe
MONEI Payments for WooCommerce / 7.3.1
MONEI Payments for WooCommerce v7.3.1
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 / YithSubscriptionPluginHandler.php

YithSubscriptionPluginHandler.php in MONEI Payments for WooCommerce 7.3.1, at src/Features/Subscriptions/YithSubscriptionPluginHandler.php

333 lines 11.3 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 WC_Order;
8 use WC_Monei_Logger;
9 use Exception;
10
11 class YithSubscriptionPluginHandler implements SubscriptionHandlerInterface {
12 private $moneiPaymentServices;
13
14 public function __construct( MoneiSdkClientFactory $sdkClient ) {
15 $this->moneiPaymentServices = new MoneiPaymentServices( $sdkClient );
16
17 // ⚠️ YITH names its renewal action after the gateway that placed the order. An
18 // express checkout of a subscription product places it under the Apple/Google
19 // Pay gateway, so listening only for the card gateway would leave that
20 // subscription with no renewal charge at all.
21 foreach ( array( MONEI_GATEWAY_ID, 'monei_apple_google' ) as $gateway_id ) {
22 add_action(
23 'ywsbs_pay_renew_order_with_' . $gateway_id,
24 function ( $renew_order ) {
25 if ( ! $renew_order instanceof WC_Order ) {
26 return false;
27 }
28 $amount_to_charge = $renew_order->get_total();
29 $this->scheduled_subscription_payment( $amount_to_charge, $renew_order );
30 },
31 10,
32 1
33 );
34 }
35 //whenever it creates a renew order it will check for this meta, as is just created we put to 0
36 add_action(
37 'ywsbs_renew_subscription',
38 function ( $order_id, $subscription_id ) {
39 $order = wc_get_order( $order_id );
40 $order->update_meta_data( 'failed_attemps', '0' );
41 $order->save();
42 },
43 10,
44 2
45 );
46 }
47
48 /**
49 * Checks if subscription plugin is enabled.
50 *
51 * @since 5.0
52 *
53 * @return bool
54 */
55 public function is_subscriptions_addon_enabled(): bool {
56 return class_exists( 'YITH_WC_Subscription' );
57 }
58
59 /**
60 * If $order_id is a subscription.
61 *
62 * @param int $order_id
63 * @return boolean
64 */
65 public function is_subscription_order( $order_id ): bool {
66 if ( ! $this->is_subscriptions_addon_enabled() ) {
67 return false;
68 }
69
70 return ( function_exists( 'ywsbs_is_an_order_with_subscription' ) && ( ywsbs_is_an_order_with_subscription( $order_id ) ) );
71 }
72
73 /**
74 * Checks if page is pay for order and change subs payment page.
75 *
76 * @return bool
77 */
78 public function is_subscription_change_payment_page(): bool {
79 return ( isset( $_GET['pay_for_order'] ) && isset( $_GET['change_payment_method'] ) ); // phpcs:ignore
80 }
81
82 public function get_subscriptions_for_order( int $order_id ): array {
83 $order = wc_get_order( $order_id );
84 return $order->get_meta( 'subscriptions' );
85 }
86
87 public function update_subscription_meta_data( $subscriptions, $payment ): void {
88 /**
89 * Iterate all subscriptions contained in the order, and add sequence id and cc data individually.
90 */
91 foreach ( $subscriptions as $subscription ) {
92 $subscription = ywsbs_get_subscription( $subscription );
93 // @phpstan-ignore-next-line
94 if ( ! $subscription ) {
95 continue;
96 }
97 // @phpstan-ignore-next-line
98 $meta = array(
99 '_monei_sequence_id' => $payment->getSequenceId(),
100 '_monei_payment_method_brand' => $payment->getPaymentMethod()->getCard()->getBrand(),
101 '_monei_payment_method_4_last_digits' => $payment->getPaymentMethod()->getCard()->getLast4(),
102 );
103 $subscription->update_subscription_meta( $meta );
104 }
105 }
106
107
108 /**
109 * If Subscription has a free trial, first payment 0 euros.
110 * We will charge customer 1 cent ( create_subscription_payload ):
111 * 1. Free Trial
112 * 2. Payment made with tokenized card ( paymentToken is set )
113 *
114 * Therefore, after the 1 cent payment, we need to refund it automatically.
115 * This hooks will trigger after successful (1 cent) payment.
116 *
117 * @param $confirm_payload
118 * @param $confirm_payment
119 * @param $order
120 *
121 * @throws \Monei\ApiException
122 */
123 public function subscription_after_payment_success( $confirm_payload, $confirm_payment, $order ): void {
124 /**
125 * If order is not subscription, bail.
126 */
127 if ( ! $this->is_subscription_order( $order->get_id() ) ) {
128 return;
129 }
130
131 /**
132 * If payment wasn't 1 cent, bail.
133 */
134 if ( 1 !== $confirm_payload['amount'] ) {
135 return;
136 }
137
138 /**
139 * If payment is not done with a tokenized card, bail.
140 */
141 if ( ! isset( $confirm_payload['paymentToken'] ) ) {
142 return;
143 }
144
145 /**
146 * Refund that cent.
147 */
148 $this->moneiPaymentServices->refund_payment( $confirm_payment->getId(), 1 );
149 }
150
151 /**
152 * It adds subscription configuration to the payload.
153 *
154 * @param $order
155 * @param $payment_method
156 *
157 * @return array
158 */
159 public function create_subscription_payload( WC_Order $order, $payment_method, $payload ): array {
160 $payload['sequence'] = array(
161 'type' => 'recurring',
162 'recurring' => array(
163 'frequency' => 1, // Testing with 1 to know if we can modify subscription dates.
164 ),
165 );
166
167 /**
168 * If there is a free trial, (first payment for free) and user has selected a tokenized card,
169 * We hit a monei limitation, so we need to charge the customer 1 cent, that will be refunded afterwards.
170 */
171 if ( 0 === monei_price_format( $order->get_total() ) && isset( $payload['paymentToken'] ) ) {
172 $payload['amount'] = 1;
173 }
174
175 /**
176 * Supporting Subscriber Payment Method Changes
177 * https://docs.woocommerce.com/document/subscriptions/develop/payment-gateway-integration/#section-18
178 *
179 * We need to charge 0, in order to get new sequence id.
180 * If customer has selected a tokenized card, because of monei restrictions
181 * we need to charge one cent, to be refunded afterwards in order to get new sequence_id.
182 */
183 if ( $this->is_subscription_change_payment_page() ) {
184 $payload['amount'] = 0;
185 if ( isset( $payload['paymentToken'] ) ) {
186 $payload['amount'] = 1;
187 }
188
189 $payload['orderId'] = $order->get_id() . '_verification' . time();
190 $payload['description'] = $payload['description'] . ' ' . __( 'Payment Method Subscription Change', 'monei' );
191 }
192
193 $payload = apply_filters( 'wc_gateway_monei_create_subscription_payload', $payload );
194 return $payload;
195 }
196
197 public function add_extra_info_to_subscriptions_payment_method_title( $payment_method_to_display, $subscription ): string {
198 // We only will modify Monei subscriptions titles.
199 if ( $subscription->get_payment_method() !== $this->id ) {
200 return $payment_method_to_display;
201 }
202 return $payment_method_to_display . ' - ' . $this->get_subscription_payment_method_friendly_name( $subscription );
203 }
204
205 /**
206 * Process payment on renewal. Woo automatically triggers this hooks once subscription needs to be renewed.
207 *
208 * @param $amount_to_charge
209 * @param WC_Order $renewal_order
210 */
211 public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ): void {
212 $description = get_bloginfo( 'name' ) . ' - #' . (string) $renewal_order->get_id() . ' - Subscription Renewal';
213 $order_id = $renewal_order->get_id();
214 $is_a_renew = $renewal_order->get_meta( 'is_a_renew' );
215 $subscription = $this->get_subscription_from_renew_order( $renewal_order );
216 $sequence_id = $this->get_sequence_id_from_subscription( $subscription );
217
218 if ( ! $subscription || 'yes' !== $is_a_renew ) {
219 WC_Monei_Logger::log( sprintf( 'Sorry, any subscription was found for order #%s or order is not a renew.', $order_id ), 'subscription_payment' );
220 }
221 $payload = array(
222 'orderId' => (string) $renewal_order->get_id(),
223 'amount' => monei_price_format( $amount_to_charge ),
224 'description' => $description,
225 );
226
227 try {
228 $payment = $this->moneiPaymentServices->recurring_payment( $sequence_id, $payload );
229
230 if ( 'SUCCEEDED' === $payment->getStatus() ) {
231 $renewal_order->payment_complete( $payment->getId() );
232
233 $order_note = __( 'Success Renewal scheduled_subscription_payment.', 'monei' ) . '<br>';
234 $order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '. <br><br>';
235 $order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
236 $renewal_order->add_order_note( $order_note );
237
238 do_action( 'wc_gateway_monei_scheduled_subscription_payment_success', $renewal_order, $amount_to_charge );
239 } else {
240 $order_note = __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . '<br>';
241 $order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '. <br><br>';
242 $order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
243 $renewal_order->update_status( 'failed' );
244 $renewal_order->add_order_note( $order_note );
245
246 do_action( 'wc_gateway_monei_scheduled_subscription_payment_not_succeeded', $renewal_order, $amount_to_charge );
247 }
248 $renewal_order->save();
249
250 } catch ( Exception $e ) {
251 do_action( 'wc_gateway_monei_scheduled_subscription_payment_error', $e, $renewal_order, $amount_to_charge );
252 WC_Monei_Logger::logError( $e );
253 $renewal_order->update_status( 'failed' );
254 $renewal_order->add_order_note( __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . $e->getMessage() );
255 $renewal_order->save();
256 if ( isset( $_REQUEST['process_early_renewal'] ) && ! wp_doing_cron() ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
257 wc_add_notice( $e->getMessage(), 'error' );
258 }
259 }
260 }
261
262 /**
263 * Get subscription object from renew order
264 *
265 * @param \WC_Order $renewal_order The WooCommerce order.
266 * @return \YWSBS_Subscription|false
267 */
268 // @phpstan-ignore-next-line
269 private function get_subscription_from_renew_order( WC_Order $renewal_order ) {
270 $subscriptions = $renewal_order->get_meta( 'subscriptions' );
271 $subscription_id = ! empty( $subscriptions ) ? array_shift( $subscriptions ) : false; // $subscriptions is always an array of 1 element.
272
273 // @phpstan-ignore-next-line
274 return $subscription_id ? ywsbs_get_subscription( $subscription_id ) : false;
275 }
276
277 public function init_subscriptions( array $supports, string $gateway_id ): array {
278 add_action( 'wc_gateway_monei_create_payment_success', array( $this, 'subscription_after_payment_success' ), 1, 3 );
279 add_action( 'woocommerce_scheduled_subscription_payment_' . $gateway_id, array( $this, 'scheduled_subscription_payment' ), 1, 2 );
280
281 // Add Payment information to Payment method name in "Subscription" Tab.
282 add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'add_extra_info_to_subscriptions_payment_method_title' ), 10, 2 );
283 return array_merge(
284 $supports,
285 array(
286 'yith_subscriptions',
287 'yith_subscriptions_multiple',
288 )
289 );
290 }
291
292 /**
293 * From renewal order, get monei sequence id.
294 *
295 * @param $subscription
296 *
297 * @return string|false
298 */
299 public function get_sequence_id_from_subscription( $subscription ) {
300 return $subscription->get( '_monei_sequence_id' );
301 }
302
303 /**
304 * Gets a readable string to present in subscription frontend.
305 *
306 * @param $subscription
307 *
308 * @return string
309 */
310 public function get_subscription_payment_method_friendly_name( $subscription ) {
311 $brand = $subscription->get_meta( '_monei_payment_method_brand', true );
312 $last_digits = $subscription->get_meta( '_monei_payment_method_4_last_digits', true );
313 /* translators: 1) card brand 2) last 4 digits */
314 return sprintf( __( '%1$s card ending in %2$s', 'monei' ), $brand, $last_digits );
315 }
316
317 /**
318 * Check if a product is a subscription using YITH WooCommerce Subscription logic
319 *
320 * @return bool
321 */
322 public function cart_has_subscription() {
323
324 if ( ! function_exists( 'YITH_WC_Subscription' ) ) {
325 return false;
326 }
327
328 $ywsbs = YITH_WC_Subscription();
329
330 return is_string( $ywsbs->cart_has_subscriptions() );
331 }
332 }
333