Cómo usar wc_create_order con un producto de suscripción
Quiero crear una nueva orden de forma programática.
Este código funciona bien con productos simples,
$product = get_product($product_id);
$order = wc_create_order();
$order->add_product( $product , 1 );
$order->calculate_totals();
// asignar la orden al usuario actual
update_post_meta($order->id, '_customer_user', get_current_user_id() );
// completar pago
$order->payment_complete();
pero cuando lo uso para un producto de suscripción no añade la suscripción, solo añade la orden.

Aquí está mi código para crear una suscripción -- me costó muchos intentos y errores resolverlo. ¡Mucha suerte!
function create_test_sub() {
$email = 'test@test.com';
$start_date = '2015-01-01 00:00:00';
$address = array(
'first_name' => 'Jeremy',
'last_name' => 'Test',
'company' => '',
'email' => $email,
'phone' => '777-777-777-777',
'address_1' => '31 Main Street',
'address_2' => '',
'city' => 'Auckland',
'state' => 'AKL',
'postcode' => '12345',
'country' => 'AU'
);
$default_password = wp_generate_password();
if (!$user = get_user_by('login', $email)) $user = wp_create_user( $email, $default_password, $email );
// He usado un producto con múltiples variaciones
$parent_product = wc_get_product(22998);
$args = array(
'attribute_billing-period' => 'Yearly',
'attribute_subscription-type' => 'Both'
);
$product_variation = $parent_product->get_matching_variation($args);
$product = wc_get_product($product_variation);
// Cada variación también tiene su propia clase de envío
$shipping_class = get_term_by('slug', $product->get_shipping_class(), 'product_shipping_class');
WC()->shipping->load_shipping_methods();
$shipping_methods = WC()->shipping->get_shipping_methods();
// Tengo alguna lógica para seleccionar qué método de envío usar; tu caso probablemente será diferente, así que determina el método que necesitas y guárdalo en $selected_shipping_method
$selected_shipping_method = $shipping_methods['free_shipping'];
$class_cost = $selected_shipping_method->get_option('class_cost_' . $shipping_class->term_id);
$quantity = 1;
// Por lo que veo, necesitas crear primero el pedido y luego la suscripción
$order = wc_create_order(array('customer_id' => $user->id));
$order->add_product( $product, $quantity, $args);
$order->set_address( $address, 'billing' );
$order->set_address( $address, 'shipping' );
$order->add_shipping((object)array (
'id' => $selected_shipping_method->id,
'label' => $selected_shipping_method->title,
'cost' => (float)$class_cost,
'taxes' => array(),
'calc_tax' => 'per_order'
));
$order->calculate_totals();
$order->update_status("completed", 'Pedido importado', TRUE);
// Pedido creado, ahora crea la suscripción asociada -- opcional si no estás creando una suscripción, obviamente
// Cada variación tiene un periodo de suscripción diferente
$period = WC_Subscriptions_Product::get_period( $product );
$interval = WC_Subscriptions_Product::get_interval( $product );
$sub = wcs_create_subscription(array('order_id' => $order->id, 'billing_period' => $period, 'billing_interval' => $interval, 'start_date' => $start_date));
$sub->add_product( $product, $quantity, $args);
$sub->set_address( $address, 'billing' );
$sub->set_address( $address, 'shipping' );
$sub->add_shipping((object)array (
'id' => $selected_shipping_method->id,
'label' => $selected_shipping_method->title,
'cost' => (float)$class_cost,
'taxes' => array(),
'calc_tax' => 'per_order'
));
$sub->calculate_totals();
WC_Subscriptions_Manager::activate_subscriptions_for_order($order);
print "<a href='/wp-admin/post.php?post=" . $sub->id . "&action=edit'>¡Suscripción creada! Haz clic aquí para editar</a>";
}

Gracias por esto, pero no logro que funcione. ¿Qué función es wcs_create_subscription()
? No está definida, y tampoco lo está wc_create_subscription()
si es un error tipográfico.

¡Necesitarás la extensión de Subscriptions para que esa parte funcione! (No olvides que puedes buscar en Google los nombres de las funciones, normalmente son muy fáciles de encontrar porque son muy específicos.)

Sí, tenía el plugin de Subscriptions, pero parece que tenía una versión anterior (1.5.). Actualicé a la versión 2.0. ahora y el código parece funcionar. ¡Muchas gracias!

¡Excelente caso de uso y ejemplo de wc_create_order + wcs_create_subscription!

Sigo recibiendo Call to undefined method WP_Error::add_product()
¿alguna idea?

Aquí hay un ejemplo
$order_data = array(
'status' => 'completed', // Estado del pedido
'customer_id' => 1, // ID del cliente
'customer_note' => '', // Nota del cliente
'total' => '', // Total
);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // Requerido, de lo contrario wc_create_order lanza una excepción
$order = wc_create_order( $order_data );

Desafortunadamente, debes manejar manualmente la creación de la suscripción mediante código, ya que simplemente agregar un pedido no lo maneja automáticamente.
Aquí está mi función personalizada que construí basándome en todas las respuestas que encontré en SO y revisando el código base de suscripciones.
Probado con
- WordPress 5.2.5
- WooCommerce 3.8.0
- WooCommerce Subscriptions 2.6.1
https://gist.github.com/tripflex/a3123052f36daf18f7cb05391d752223
function give_user_subscription( $product, $user_id, $note = '' ){
// Primero verifica que todas las funciones y clases requeridas existan
if( ! function_exists( 'wc_create_order' ) || ! function_exists( 'wcs_create_subscription' ) || ! class_exists( 'WC_Subscriptions_Product' ) ){
return false;
}
$order = wc_create_order( array( 'customer_id' => $user_id ) );
if( is_wp_error( $order ) ){
return false;
}
$user = get_user_by( 'ID', $user_id );
$fname = $user->first_name;
$lname = $user->last_name;
$email = $user->user_email;
$address_1 = get_user_meta( $user_id, 'billing_address_1', true );
$address_2 = get_user_meta( $user_id, 'billing_address_2', true );
$city = get_user_meta( $user_id, 'billing_city', true );
$postcode = get_user_meta( $user_id, 'billing_postcode', true );
$country = get_user_meta( $user_id, 'billing_country', true );
$state = get_user_meta( $user_id, 'billing_state', true );
$address = array(
'first_name' => $fname,
'last_name' => $lname,
'email' => $email,
'address_1' => $address_1,
'address_2' => $address_2,
'city' => $city,
'state' => $state,
'postcode' => $postcode,
'country' => $country,
);
$order->set_address( $address, 'billing' );
$order->set_address( $address, 'shipping' );
$order->add_product( $product, 1 );
$sub = wcs_create_subscription(array(
'order_id' => $order->get_id(),
'status' => 'pending', // El estado debe establecerse inicialmente como pendiente para coincidir con el proceso normal de pago
'billing_period' => WC_Subscriptions_Product::get_period( $product ),
'billing_interval' => WC_Subscriptions_Product::get_interval( $product )
));
if( is_wp_error( $sub ) ){
return false;
}
// Modelado según WC_Subscriptions_Cart::calculate_subscription_totals()
$start_date = gmdate( 'Y-m-d H:i:s' );
// Agrega producto a la suscripción
$sub->add_product( $product, 1 );
$dates = array(
'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date( $product, $start_date ),
'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date( $product, $start_date ),
'end' => WC_Subscriptions_Product::get_expiration_date( $product, $start_date ),
);
$sub->update_dates( $dates );
$sub->calculate_totals();
// Actualiza el estado del pedido con nota personalizada
$note = ! empty( $note ) ? $note : __( 'Pedido y suscripción agregados programáticamente.' );
$order->update_status( 'completed', $note, true );
// También actualiza el estado de la suscripción a activo desde pendiente (y agrega nota)
$sub->update_status( 'active', $note, true );
return $sub;
}
