File "class-kco-subscription.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/klarna-checkout-for-woocommerce/classes/class-kco-subscription.php
File size: 21.97 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Subscription handler.
*
* @package Klarna_Checkout/Classes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Handles subscription payments with Klarna checkout.
*
* @class Klarna_Checkout_Subscription
* @version 1.0
* @package Klarna_Checkout/Classes
* @category Class
* @author Krokedil
*/
class KCO_Subscription {
/**
* Class constructor.
*/
public function __construct() {
add_filter( 'kco_wc_api_request_args', array( $this, 'create_extra_merchant_data' ) );
add_filter( 'kco_wc_api_request_args', array( $this, 'set_recurring' ) );
add_filter( 'kco_wc_api_hpp_request_args', array( $this, 'change_return_url_for_recurring_change_payment_method' ), 10, 3 );
add_action( 'kco_wc_payment_complete', array( $this, 'set_recurring_token_for_order' ), 10, 2 );
add_action( 'woocommerce_scheduled_subscription_payment_kco', array( $this, 'trigger_scheduled_payment' ), 10, 2 );
add_action( 'woocommerce_admin_order_data_after_billing_address', array( $this, 'show_recurring_token' ) );
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'save_kco_recurring_token_update' ), 45, 2 );
add_action( 'wc_klarna_push_cb', array( $this, 'handle_push_cb_for_payment_method_change' ) );
add_action( 'init', array( $this, 'display_thankyou_message_for_payment_method_change' ) );
add_action( 'woocommerce_account_view-subscription_endpoint', array( $this, 'maybe_confirm_change_payment_method' ) );
add_filter( 'allowed_redirect_hosts', array( $this, 'extend_allowed_domains_list' ) );
}
/**
* Checks the cart if it has a subscription product in it.
*
* @return bool
*/
public function check_if_subscription() {
if ( class_exists( 'WC_Subscriptions_Cart' ) && ( WC_Subscriptions_Cart::cart_contains_subscription() || wcs_cart_contains_renewal() ) ) {
return true;
}
return false;
}
/**
* Checks if this is a KCO subscription payment method change.
*
* @return bool
*/
public function is_kco_subs_change_payment_method() {
$key = filter_input( INPUT_GET, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$change_payment_method = filter_input( INPUT_GET, 'change_payment_method', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $key ) && ( ! empty( $change_payment_method ) ) ) {
return true;
}
return false;
}
/**
* Creates the extra merchant data array needed for a subscription.
*
* @param array $request_args The Klarna request arguments.
* @return array
*/
public function create_extra_merchant_data( $request_args ) {
if ( class_exists( 'WC_Subscriptions_Cart' ) && WC_Subscriptions_Cart::cart_contains_subscription() ) {
$subscription_product_id = false;
if ( ! empty( WC()->cart->cart_contents ) ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['product_id'] ) ) {
$subscription_product_id = $cart_item['product_id'];
break;
}
}
}
if ( $subscription_product_id ) {
$subscription_expiration_time = WC_Subscriptions_Product::get_expiration_date( $subscription_product_id );
if ( 0 !== $subscription_expiration_time ) {
$end_time = date( 'Y-m-d\TH:i', strtotime( $subscription_expiration_time ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display.
} else {
$end_time = date( 'Y-m-d\TH:i', strtotime( '+50 year' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display.
}
$emd_subscription = array(
'subscription_name' => 'Subscription: ' . get_the_title( $subscription_product_id ),
'start_time' => date( 'Y-m-d\TH:i' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display.
'end_time' => $end_time,
'auto_renewal_of_subscription' => false,
);
if ( is_user_logged_in() ) {
// User is logged in - add user_login as unique_account_identifier.
$current_user = wp_get_current_user();
$emd_account = array(
'unique_account_identifier' => $current_user->user_login,
'account_registration_date' => date( 'Y-m-d\TH:i', strtotime( $current_user->user_registered ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display.
'account_last_modified' => date( 'Y-m-d\TH:i' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display.
);
} else {
// User is not logged in - send empty params.
$emd_account = array(
'unique_account_identifier' => '',
);
}
$emd = array(
'Subscription' => array( $emd_subscription ),
'customer_account_info' => array( $emd_account ),
);
$request_args['attachment']['content_type'] = 'application/vnd.klarna.internal.emd-v2+json';
$request_args['attachment']['body'] = wp_json_encode( $emd );
}
}
return $request_args;
}
/**
* Marks the order as a recurring order for Klarna
*
* @param array $request_args The Klarna request arguments.
* @return array
*/
public function set_recurring( $request_args ) {
// Check if we have a subscription product. If yes set recurring field.
if ( $this->check_if_subscription() || $this->is_kco_subs_change_payment_method() ) {
$request_args['recurring'] = true;
}
// If this is a change payment method request.
if ( $this->is_kco_subs_change_payment_method() ) {
$key = filter_input( INPUT_GET, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$order_id = wc_get_order_id_by_order_key( $key );
if ( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( is_object( $wc_order ) && function_exists( 'wcs_order_contains_subscription' ) && function_exists( 'wcs_is_subscription' ) ) {
if ( wcs_order_contains_subscription( $wc_order, array( 'parent', 'renewal', 'resubscribe', 'switch' ) ) || wcs_is_subscription( $wc_order ) ) {
// Modify order lines.
$order_lines = array();
foreach ( $wc_order->get_items() as $item ) {
$order_lines[] = array(
'name' => $item->get_name(),
'quantity' => $item->get_quantity(),
'total_amount' => 0,
'unit_price' => 0,
'total_tax_amount' => 0,
'tax_rate' => 0,
);
}
$request_args['order_lines'] = $order_lines;
$request_args['order_tax_amount'] = 0;
$request_args['order_amount'] = 0;
// Modify merchant url's.
global $wp;
$query_string = filter_input( INPUT_SERVER, 'QUERY_STRING', FILTER_SANITIZE_URL );
$current_url = add_query_arg( $query_string, '', home_url( $wp->request ) );
$confirmation_url = add_query_arg(
array(
'kco-action' => 'subs-payment-changed',
'kco-order-id' => '{checkout.order.id}',
),
$wc_order->get_view_order_url()
);
$push_url = add_query_arg(
array(
'kco-action' => 'subs-payment-changed',
'key' => sanitize_key( $order_id ),
),
$request_args['merchant_urls']['push']
);
unset( $request_args['merchant_urls']['validation'] );
unset( $request_args['merchant_urls']['shipping_option_update'] );
unset( $request_args['options']['require_client_validation'] );
unset( $request_args['options']['require_client_validation_callback_response'] );
$request_args['merchant_urls']['checkout'] = $current_url;
$request_args['merchant_urls']['confirmation'] = $confirmation_url;
$request_args['merchant_urls']['push'] = $push_url;
}
}
}
}
return $request_args;
}
/**
* Changes the success URL for HPP payments if this is a subscription payment method change.
*
* @param array $request_args The Klarna HPP request arguments.
* @param int $order_id The WooCommerce order ID.
* @param array $session_id The Klarna Checkout order ID.
* @return array
*/
public function change_return_url_for_recurring_change_payment_method( $request_args, $order_id, $session_id ) {
// If this is a change payment method request.
if ( $this->is_kco_subs_change_payment_method() ) {
$order = wc_get_order( $order_id );
if ( is_object( $order ) ) {
$success_url = add_query_arg(
array(
'kco-action' => 'subs-payment-changed',
'hppid' => '{{session_id}}',
'kco-order-id' => $session_id,
),
$order->get_view_order_url()
);
$request_args['merchant_urls']['success'] = $success_url;
}
}
return $request_args;
}
/**
* Sets the recurring token for the subscription order
*
* @param int $order_id The WooCommerce order id.
* @param array $klarna_order The Klarna order.
* @return void
*/
public function set_recurring_token_for_order( $order_id = null, $klarna_order = null ) {
$wc_order = wc_get_order( $order_id );
$recurring_order = $wc_order->get_meta( '_kco_recurring_order', true );
if ( 'yes' === $recurring_order || class_exists( 'WC_Subscription' ) && ( wcs_order_contains_subscription( $wc_order, array( 'parent', 'renewal', 'resubscribe', 'switch' ) ) || wcs_is_subscription( $wc_order ) ) ) {
$subscriptions = wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => 'any' ) );
$klarna_order_id = $wc_order->get_transaction_id();
$klarna_order = KCO_WC()->api->get_klarna_order( $klarna_order_id );
if ( isset( $klarna_order['recurring_token'] ) ) {
$recurring_token = $klarna_order['recurring_token'];
// translators: %s Klarna recurring token.
$note = sprintf( __( 'Recurring token for subscription: %s', 'klarna-checkout-for-woocommerce' ), sanitize_key( $recurring_token ) );
$wc_order->add_order_note( $note );
foreach ( $subscriptions as $subscription ) {
$subscription->update_meta_data( '_kco_recurring_token', $recurring_token );
$subscription->add_order_note( $note );
// Do not overwrite any existing phone number in case the customer has changed payment method (and thus shipping details).
if ( empty( $subscription->get_shipping_phone() ) ) {
// NOTE: Since we declare support for WC v4+, and WC_Order::set_shipping_phone was only added in 5.6.0, we need to use update_meta_data instead. There is no default shipping email field in WC.
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '5.6.0', '>=' ) ) {
$subscription->set_shipping_phone( $klarna_order['shipping_address']['phone'] );
} else {
$subscription->update_meta_data( '_shipping_phone', $klarna_order['shipping_address']['phone'] );
}
}
$subscription->save();
}
// Also update the renewal order with the new recurring token.
$wc_order->update_meta_data( '_kco_recurring_token', sanitize_key( $recurring_token ) );
} else {
$wc_order->add_order_note( __( 'Recurring token was missing from the Klarna order during the checkout process. Please contact Klarna for help.', 'klarna-checkout-for-woocommerce' ) );
$wc_order->set_status( 'on-hold' );
foreach ( $subscriptions as $subscription ) {
$subscription->set_status( 'on-hold' );
}
}
$wc_order->save();
}
}
/**
* Sets the recurring token for a subscription
*
* @param int $subscription_id The WooCommerce Subscription ID.
* @param array $klarna_order The Klarna order.
* @return void
*/
public function set_recurring_token_for_subscription( $subscription_id = null, $klarna_order = null ) {
if ( isset( $klarna_order['recurring_token'] ) ) {
$recurring_token = $klarna_order['recurring_token'];
$subscription_order = wc_get_order( $subscription_id );
$subscription_order->update_meta_data( '_kco_recurring_token', $recurring_token );
$subscription_order->save();
}
}
/**
* Creates an order in Klarna from the recurring token saved.
*
* @param string $renewal_total The total price for the order.
* @param object $renewal_order The WooCommerce order for the renewal.
*/
public function trigger_scheduled_payment( $renewal_total, $renewal_order ) {
$order_id = $renewal_order->get_id();
$subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order->get_id() );
$recurring_token = $renewal_order->get_meta( '_kco_recurring_token', true );
if ( empty( $recurring_token ) ) {
// Try getting it from parent order.
$recurring_token = wc_get_order( WC_Subscriptions_Renewal_Order::get_parent_order_id( $order_id ) )->get_meta( '_kco_recurring_token', true );
$renewal_order->update_meta_data( '_kco_recurring_token', $recurring_token );
}
if ( empty( $recurring_token ) ) {
// Try getting it from _klarna_recurring_token (the old Klarna plugin).
$recurring_token = $renewal_order->get_meta( '_klarna_recurring_token', true );
if ( empty( $recurring_token ) ) {
$recurring_token = wc_get_order( WC_Subscriptions_Renewal_Order::get_parent_order_id( $order_id ) )->get_meta( '_klarna_recurring_token', true );
$renewal_order->update_meta_data( '_klarna_recurring_token', $recurring_token );
}
if ( ! empty( $recurring_token ) ) {
$renewal_order->update_meta_data( '_kco_recurring_token', $recurring_token );
foreach ( $subscriptions as $subscription ) {
$subscription_order = wc_get_order( $subscription->get_id() );
$subscription_order->update_meta_data( '_kco_recurring_token', $recurring_token );
$subscription_order->save();
}
}
}
$renewal_order->save();
$create_order_response = KCO_WC()->api->create_recurring_order( $order_id, $recurring_token );
if ( ! is_wp_error( $create_order_response ) ) {
$klarna_order_id = $create_order_response['order_id'];
// Translators: Klarna order id.
$renewal_order->add_order_note( sprintf( __( 'Subscription payment made with Klarna. Klarna order id: %s', 'klarna-checkout-for-woocommerce' ), $klarna_order_id ) );
foreach ( $subscriptions as $subscription ) {
$subscription->payment_complete( $klarna_order_id );
}
} else {
$error_message = $create_order_response->get_error_message();
// Translators: Error message.
$renewal_order->add_order_note( sprintf( __( 'Subscription payment failed with Klarna. Message: %1$s', 'klarna-checkout-for-woocommerce' ), $error_message ) );
foreach ( $subscriptions as $subscription ) {
$subscription->payment_failed();
}
}
}
/**
* Shows the recurring token for the order.
*
* @param WC_Order $order The WooCommerce order.
* @return void
*/
public function show_recurring_token( $order ) {
if ( 'shop_subscription' === $order->get_type() ) {
$recurring_token = $order->get_meta( '_kco_recurring_token', true );
if ( empty( $recurring_token ) ) {
$parent = $order->get_parent() ?? false;
$recurring_token = ! empty( $parent ) ? $parent->get_meta( '_kco_recurring_token', true ) : '';
}
?>
<div class="order_data_column" style="clear:both; float:none; width:100%;">
<div class="address">
<p>
<strong><?php echo esc_html( 'Klarna recurring token' ); ?>:</strong><?php echo esc_html( $recurring_token ); ?>
</p>
</div>
<div class="edit_address">
<?php
woocommerce_wp_text_input(
array(
'id' => '_kco_recurring_token',
'label' => __( 'Klarna recurring token', 'klarna-checkout-for-woocommerce' ),
'wrapper_class' => '_billing_company_field',
'value' => $recurring_token,
)
);
?>
</div>
</div>
<?php
}
}
/**
* Saves the recurring token.
*
* @param int $post_id WordPress post id.
* @param WP_Post $post The WordPress post.
* @return void
*/
public function save_kco_recurring_token_update( $post_id, $post ) {
$klarna_recurring_token = filter_input( INPUT_POST, '_kco_recurring_token', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$order = wc_get_order( $post_id );
if ( 'shop_subscription' === $order->get_type() && $order->get_meta( '_kco_recurring_token', true ) ) {
$order->update_meta_data( '_kco_recurring_token', $klarna_recurring_token );
$order->save();
}
}
/**
* Handle push callback from Klarna if this is a KCO subscription payment method change.
*
* @param string $klarna_order_id The order id for the Klarna order.
* @return void
*/
public function handle_push_cb_for_payment_method_change( $klarna_order_id ) {
$subscription_id = filter_input( INPUT_GET, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$kco_action = filter_input( INPUT_GET, 'kco-action', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $subscription_id ) && ( ! empty( $kco_action ) && 'subs-payment-changed' === $kco_action ) ) {
$subscription = wcs_get_subscription( $subscription_id );
// Add recurring token to order via Checkout API.
$klarna_order = KCO_WC()->api->get_klarna_order( $klarna_order_id );
if ( ! is_wp_error( $klarna_order ) ) {
if ( isset( $klarna_order['recurring_token'] ) && ! empty( $klarna_order['recurring_token'] ) ) {
$subscription_order = wc_get_order( $subscription->get_id() );
$subscription_order->update_meta_data( '_kco_recurring_token', sanitize_key( $klarna_order['recurring_token'] ) );
$subscription_order->save();
// translators: %s Klarna recurring token.
$note = sprintf( __( 'Payment method changed via Klarna Checkout. New recurring token for subscription: %s', 'klarna-checkout-for-woocommerce' ), sanitize_key( $klarna_order['recurring_token'] ) );
$subscription->add_order_note( $note );
}
} else {
// Retrieve error.
$error_message = $klarna_order->get_error_message();
$note = sprintf( __( 'Could not retrieve new Klarna recurring token for subscription when customer changed payment method. Read the log for detailed information.', 'klarna-checkout-for-woocommerce' ), $error_message );
$subscription->add_order_note( $note );
}
// Acknowledge order in Klarna.
KCO_WC()->api->acknowledge_klarna_order( $klarna_order_id );
KCO_WC()->api->set_merchant_reference( $klarna_order_id, $subscription_id );
exit;
}
}
/**
* Display thankyou notice when customer is redirected back to the
* subscription page in front-end after changing payment method.
*
* @return void
*/
public function display_thankyou_message_for_payment_method_change() {
$kco_action = filter_input( INPUT_GET, 'kco-action', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $kco_action ) && 'subs-payment-changed' === $kco_action ) {
wc_add_notice( __( 'Thank you, your subscription payment method is now updated.', 'klarna-checkout-for-woocommerce' ), 'success' );
kco_unset_sessions();
}
}
/**
* Maybe confirm the change payment method of a Klarna subscription.
*
* @param int $subscription_id The WooCommerce Subscription ID.
* @return void
*/
public function maybe_confirm_change_payment_method( $subscription_id ) {
$klarna_order_id = filter_input( INPUT_GET, 'kco-order-id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $klarna_order_id ) ) {
$klarna_order = KCO_WC()->api->get_klarna_order( $klarna_order_id );
$this->set_recurring_token_for_subscription( $subscription_id, $klarna_order );
$this->update_subscription_address( $subscription_id, $klarna_order );
}
}
/**
* Update the address for a subscription after changing payment method.
*
* @param int $subscription_id The ID of the WooCommerce Subscription.
* @param array $klarna_order The Klarna order.
* @return void
*/
public function update_subscription_address( $subscription_id, $klarna_order ) {
$subscription = wcs_get_subscription( $subscription_id );
$subscription->set_billing_first_name( $klarna_order['billing_address']['given_name'] );
$subscription->set_billing_last_name( $klarna_order['billing_address']['family_name'] );
$subscription->set_billing_address_1( $klarna_order['billing_address']['street_address'] );
if ( isset( $klarna_order['billing_address']['street_address2'] ) ) {
$subscription->set_billing_address_2( $klarna_order['billing_address']['street_address2'] );
}
$subscription->set_billing_country( strtoupper( $klarna_order['billing_address']['country'] ) );
$subscription->set_billing_postcode( $klarna_order['billing_address']['postal_code'] );
$subscription->set_billing_city( $klarna_order['billing_address']['city'] );
$subscription->set_billing_email( $klarna_order['billing_address']['email'] );
$subscription->set_billing_phone( $klarna_order['billing_address']['phone'] );
$subscription->set_shipping_first_name( $klarna_order['shipping_address']['given_name'] );
$subscription->set_shipping_last_name( $klarna_order['shipping_address']['family_name'] );
$subscription->set_shipping_address_1( $klarna_order['shipping_address']['street_address'] );
if ( isset( $klarna_order['shipping_address']['street_address2'] ) ) {
$subscription->set_shipping_address_2( $klarna_order['shipping_address']['street_address2'] );
}
$subscription->set_shipping_country( strtoupper( $klarna_order['shipping_address']['country'] ) );
$subscription->set_shipping_postcode( $klarna_order['shipping_address']['postal_code'] );
$subscription->set_shipping_city( $klarna_order['shipping_address']['city'] );
// NOTE: Since we declare support for WC v4+, and WC_Order::set_shipping_phone was only added in 5.6.0, we need to use update_meta_data instead. There is no default shipping email field in WC.
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '5.6.0', '>=' ) ) {
$subscription->set_shipping_phone( $klarna_order['shipping_address']['phone'] );
} else {
$subscription->update_meta_data( '_shipping_phone', $klarna_order['shipping_address']['phone'] );
}
$subscription->save();
}
/**
* Add Klarna hosted payment page as allowed external url for wp_safe_redirect.
* We do this because WooCommerce Subscriptions use wp_safe_redirect when processing a payment method change request (from v5.1.0).
*
* @param array $hosts Domains that are allowed when wp_safe_redirect is used.
* @return array
*/
public function extend_allowed_domains_list( $hosts ) {
$hosts[] = 'pay.playground.klarna.com';
$hosts[] = 'pay.klarna.com';
return $hosts;
}
}
new KCO_Subscription();