moneiPaymentServices = new MoneiPaymentServices( $sdkClient );
}
/**
* Checks if subscription plugin is enabled.
*
* @since 5.0
*
* @return bool
*/
public function is_subscriptions_addon_enabled(): bool {
return class_exists( 'WC_Subscriptions' );
}
public function is_subscription_order( int $order_id ): bool {
if ( ! $this->is_subscriptions_addon_enabled() ) {
return false;
}
return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription(
$order_id
) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $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 {
// new WC_Subscription( $order_id );
return wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => array( 'any' ) ) );
}
/**
* 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 {
$sequence_id = $this->get_sequence_id_from_renewal_order( $renewal_order );
$description = get_bloginfo( 'name' ) . ' - #' . (string) $renewal_order->get_id() . ' - Subscription Renewal';
$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' );
}
}
}
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_id => $subscription ) {
$subscription->update_meta_data( '_monei_sequence_id', $payment->getSequenceId() );
$subscription->update_meta_data( '_monei_payment_method_brand', $payment->getPaymentMethod()->getCard()->getBrand() );
$subscription->update_meta_data( '_monei_payment_method_4_last_digits', $payment->getPaymentMethod()->getCard()->getLast4() );
$subscription->save_meta_data();
}
}
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(
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
'subscription_reactivation',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change',
'subscription_payment_method_change_customer',
'multiple_subscriptions',
)
);
}
public function get_cart_subscription_interval_in_days() {
if ( WC()->cart === null ) {
return 0;
}
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$interval = WC_Subscriptions_Product::get_interval( $cart_item['data'] );
$period = WC_Subscriptions_Product::get_period( $cart_item['data'] );
break;
}
}
switch ( $period ) {
case 'year':
$interval_in_days = $interval * 365;
break;
case 'month':
$interval_in_days = $interval * 28; // Monei Needs minimun days, to be safe, 28.
break;
case 'week':
$interval_in_days = $interval * 7;
break;
default:
$interval_in_days = $interval;
break;
}
return $interval_in_days;
}
/**
* Retrieves parent order id from a renewal order.
*
* @param $renewal_order
*
* @return false|WC_Order
*/
public function get_parent_for_renewal_order_id( $renewal_order ) {
$subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order );
$subscription = array_pop( $subscriptions );
if ( 0 === $subscription->get_parent_id() ) {
$parent_order = null;
} else {
$parent_order = $subscription->get_parent();
}
return $parent_order;
}
/**
* Retrieves parent order from a subscription order.
*
* @param \WCS_Subscription $subscription_order
*
* @return \WC_Order|false
*/
public function get_parent_for_subscription_id( $subscription_order ) {
return $subscription_order->get_parent();
}
/**
* From renewal order, get monei sequence id.
*
* @param $renewal_order
*
* @return string|false
*/
public function get_sequence_id_from_renewal_order( $renewal_order ) {
return $renewal_order->get_meta( '_monei_sequence_id', true );
}
/**
* 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 WooCommerce Subscription logic
*
* @return bool
*/
public function cart_has_subscription() {
if ( ! $this->is_subscriptions_addon_enabled() ) {
return false;
}
if ( WC()->cart === null ) {
return false;
}
return is_array( WC()->cart->recurring_carts ) && count( WC()->cart->recurring_carts ) > 0;
}
}