moneiPaymentServices = new MoneiPaymentServices( $sdkClient );
// ⚠️ YITH names its renewal action after the gateway that placed the order. An
// express checkout of a subscription product places it under the Apple/Google
// Pay gateway, so listening only for the card gateway would leave that
// subscription with no renewal charge at all.
foreach ( array( MONEI_GATEWAY_ID, 'monei_apple_google' ) as $gateway_id ) {
add_action(
'ywsbs_pay_renew_order_with_' . $gateway_id,
function ( $renew_order ) {
if ( ! $renew_order instanceof WC_Order ) {
return false;
}
$amount_to_charge = $renew_order->get_total();
$this->scheduled_subscription_payment( $amount_to_charge, $renew_order );
},
10,
1
);
}
//whenever it creates a renew order it will check for this meta, as is just created we put to 0
add_action(
'ywsbs_renew_subscription',
function ( $order_id, $subscription_id ) {
$order = wc_get_order( $order_id );
$order->update_meta_data( 'failed_attemps', '0' );
$order->save();
},
10,
2
);
}
/**
* Checks if subscription plugin is enabled.
*
* @since 5.0
*
* @return bool
*/
public function is_subscriptions_addon_enabled(): bool {
return class_exists( 'YITH_WC_Subscription' );
}
/**
* If $order_id is a subscription.
*
* @param int $order_id
* @return boolean
*/
public function is_subscription_order( $order_id ): bool {
if ( ! $this->is_subscriptions_addon_enabled() ) {
return false;
}
return ( function_exists( 'ywsbs_is_an_order_with_subscription' ) && ( ywsbs_is_an_order_with_subscription( $order_id ) ) );
}
/**
* Checks if page is pay for order and change subs payment page.
*
* @return bool
*/
public function is_subscription_change_payment_page(): bool {
return ( isset( $_GET['pay_for_order'] ) && isset( $_GET['change_payment_method'] ) ); // phpcs:ignore
}
public function get_subscriptions_for_order( int $order_id ): array {
$order = wc_get_order( $order_id );
return $order->get_meta( 'subscriptions' );
}
public function update_subscription_meta_data( $subscriptions, $payment ): void {
/**
* Iterate all subscriptions contained in the order, and add sequence id and cc data individually.
*/
foreach ( $subscriptions as $subscription ) {
$subscription = ywsbs_get_subscription( $subscription );
// @phpstan-ignore-next-line
if ( ! $subscription ) {
continue;
}
// @phpstan-ignore-next-line
$meta = array(
'_monei_sequence_id' => $payment->getSequenceId(),
'_monei_payment_method_brand' => $payment->getPaymentMethod()->getCard()->getBrand(),
'_monei_payment_method_4_last_digits' => $payment->getPaymentMethod()->getCard()->getLast4(),
);
$subscription->update_subscription_meta( $meta );
}
}
/**
* If Subscription has a free trial, first payment 0 euros.
* We will charge customer 1 cent ( create_subscription_payload ):
* 1. Free Trial
* 2. Payment made with tokenized card ( paymentToken is set )
*
* Therefore, after the 1 cent payment, we need to refund it automatically.
* This hooks will trigger after successful (1 cent) payment.
*
* @param $confirm_payload
* @param $confirm_payment
* @param $order
*
* @throws \Monei\ApiException
*/
public function subscription_after_payment_success( $confirm_payload, $confirm_payment, $order ): void {
/**
* If order is not subscription, bail.
*/
if ( ! $this->is_subscription_order( $order->get_id() ) ) {
return;
}
/**
* If payment wasn't 1 cent, bail.
*/
if ( 1 !== $confirm_payload['amount'] ) {
return;
}
/**
* If payment is not done with a tokenized card, bail.
*/
if ( ! isset( $confirm_payload['paymentToken'] ) ) {
return;
}
/**
* Refund that cent.
*/
$this->moneiPaymentServices->refund_payment( $confirm_payment->getId(), 1 );
}
/**
* It adds subscription configuration to the payload.
*
* @param $order
* @param $payment_method
*
* @return array
*/
public function create_subscription_payload( WC_Order $order, $payment_method, $payload ): array {
$payload['sequence'] = array(
'type' => 'recurring',
'recurring' => array(
'frequency' => 1, // Testing with 1 to know if we can modify subscription dates.
),
);
/**
* If there is a free trial, (first payment for free) and user has selected a tokenized card,
* We hit a monei limitation, so we need to charge the customer 1 cent, that will be refunded afterwards.
*/
if ( 0 === monei_price_format( $order->get_total() ) && isset( $payload['paymentToken'] ) ) {
$payload['amount'] = 1;
}
/**
* Supporting Subscriber Payment Method Changes
* https://docs.woocommerce.com/document/subscriptions/develop/payment-gateway-integration/#section-18
*
* We need to charge 0, in order to get new sequence id.
* If customer has selected a tokenized card, because of monei restrictions
* we need to charge one cent, to be refunded afterwards in order to get new sequence_id.
*/
if ( $this->is_subscription_change_payment_page() ) {
$payload['amount'] = 0;
if ( isset( $payload['paymentToken'] ) ) {
$payload['amount'] = 1;
}
$payload['orderId'] = $order->get_id() . '_verification' . time();
$payload['description'] = $payload['description'] . ' ' . __( 'Payment Method Subscription Change', 'monei' );
}
$payload = apply_filters( 'wc_gateway_monei_create_subscription_payload', $payload );
return $payload;
}
public function add_extra_info_to_subscriptions_payment_method_title( $payment_method_to_display, $subscription ): string {
// We only will modify Monei subscriptions titles.
if ( $subscription->get_payment_method() !== $this->id ) {
return $payment_method_to_display;
}
return $payment_method_to_display . ' - ' . $this->get_subscription_payment_method_friendly_name( $subscription );
}
/**
* Process payment on renewal. Woo automatically triggers this hooks once subscription needs to be renewed.
*
* @param $amount_to_charge
* @param WC_Order $renewal_order
*/
public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ): void {
$description = get_bloginfo( 'name' ) . ' - #' . (string) $renewal_order->get_id() . ' - Subscription Renewal';
$order_id = $renewal_order->get_id();
$is_a_renew = $renewal_order->get_meta( 'is_a_renew' );
$subscription = $this->get_subscription_from_renew_order( $renewal_order );
$sequence_id = $this->get_sequence_id_from_subscription( $subscription );
if ( ! $subscription || 'yes' !== $is_a_renew ) {
WC_Monei_Logger::log( sprintf( 'Sorry, any subscription was found for order #%s or order is not a renew.', $order_id ), 'subscription_payment' );
}
$payload = array(
'orderId' => (string) $renewal_order->get_id(),
'amount' => monei_price_format( $amount_to_charge ),
'description' => $description,
);
try {
$payment = $this->moneiPaymentServices->recurring_payment( $sequence_id, $payload );
if ( 'SUCCEEDED' === $payment->getStatus() ) {
$renewal_order->payment_complete( $payment->getId() );
$order_note = __( 'Success Renewal scheduled_subscription_payment.', 'monei' ) . '
';
$order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '.
';
$order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
$renewal_order->add_order_note( $order_note );
do_action( 'wc_gateway_monei_scheduled_subscription_payment_success', $renewal_order, $amount_to_charge );
} else {
$order_note = __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . '
';
$order_note .= __( 'MONEI Transaction id: ', 'monei' ) . $payment->getId() . '.
';
$order_note .= __( 'MONEI Status Message: ', 'monei' ) . $payment->getStatusMessage();
$renewal_order->update_status( 'failed' );
$renewal_order->add_order_note( $order_note );
do_action( 'wc_gateway_monei_scheduled_subscription_payment_not_succeeded', $renewal_order, $amount_to_charge );
}
$renewal_order->save();
} catch ( Exception $e ) {
do_action( 'wc_gateway_monei_scheduled_subscription_payment_error', $e, $renewal_order, $amount_to_charge );
WC_Monei_Logger::logError( $e );
$renewal_order->update_status( 'failed' );
$renewal_order->add_order_note( __( 'Error Renewal scheduled_subscription_payment. Reason: ', 'monei' ) . $e->getMessage() );
$renewal_order->save();
if ( isset( $_REQUEST['process_early_renewal'] ) && ! wp_doing_cron() ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
wc_add_notice( $e->getMessage(), 'error' );
}
}
}
/**
* Get subscription object from renew order
*
* @param \WC_Order $renewal_order The WooCommerce order.
* @return \YWSBS_Subscription|false
*/
// @phpstan-ignore-next-line
private function get_subscription_from_renew_order( WC_Order $renewal_order ) {
$subscriptions = $renewal_order->get_meta( 'subscriptions' );
$subscription_id = ! empty( $subscriptions ) ? array_shift( $subscriptions ) : false; // $subscriptions is always an array of 1 element.
// @phpstan-ignore-next-line
return $subscription_id ? ywsbs_get_subscription( $subscription_id ) : false;
}
public function init_subscriptions( array $supports, string $gateway_id ): array {
add_action( 'wc_gateway_monei_create_payment_success', array( $this, 'subscription_after_payment_success' ), 1, 3 );
add_action( 'woocommerce_scheduled_subscription_payment_' . $gateway_id, array( $this, 'scheduled_subscription_payment' ), 1, 2 );
// Add Payment information to Payment method name in "Subscription" Tab.
add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'add_extra_info_to_subscriptions_payment_method_title' ), 10, 2 );
return array_merge(
$supports,
array(
'yith_subscriptions',
'yith_subscriptions_multiple',
)
);
}
/**
* From renewal order, get monei sequence id.
*
* @param $subscription
*
* @return string|false
*/
public function get_sequence_id_from_subscription( $subscription ) {
return $subscription->get( '_monei_sequence_id' );
}
/**
* Gets a readable string to present in subscription frontend.
*
* @param $subscription
*
* @return string
*/
public function get_subscription_payment_method_friendly_name( $subscription ) {
$brand = $subscription->get_meta( '_monei_payment_method_brand', true );
$last_digits = $subscription->get_meta( '_monei_payment_method_4_last_digits', true );
/* translators: 1) card brand 2) last 4 digits */
return sprintf( __( '%1$s card ending in %2$s', 'monei' ), $brand, $last_digits );
}
/**
* Check if a product is a subscription using YITH WooCommerce Subscription logic
*
* @return bool
*/
public function cart_has_subscription() {
if ( ! function_exists( 'YITH_WC_Subscription' ) ) {
return false;
}
$ywsbs = YITH_WC_Subscription();
return is_string( $ywsbs->cart_has_subscriptions() );
}
}