finalizing subscription moduile, ready to test
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace WooNooW\Api;
|
||||
|
||||
use WP_Error;
|
||||
@@ -8,40 +9,44 @@ use WC_Product;
|
||||
use WC_Shipping_Zones;
|
||||
use WC_Shipping_Rate;
|
||||
|
||||
if (!defined('ABSPATH')) { exit; }
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CheckoutController {
|
||||
class CheckoutController
|
||||
{
|
||||
|
||||
/**
|
||||
* Register REST routes for checkout quote & submit
|
||||
*/
|
||||
public static function register() {
|
||||
public static function register()
|
||||
{
|
||||
$namespace = 'woonoow/v1';
|
||||
register_rest_route($namespace, '/checkout/quote', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ new self(), 'quote' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ], // consider nonce check later
|
||||
'callback' => [new self(), 'quote'],
|
||||
'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'], // consider nonce check later
|
||||
]);
|
||||
register_rest_route($namespace, '/checkout/submit', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ new self(), 'submit' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ], // consider capability/nonce
|
||||
'callback' => [new self(), 'submit'],
|
||||
'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'], // consider capability/nonce
|
||||
]);
|
||||
register_rest_route($namespace, '/checkout/fields', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ new self(), 'get_fields' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ],
|
||||
'callback' => [new self(), 'get_fields'],
|
||||
'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'],
|
||||
]);
|
||||
// Public countries endpoint for customer checkout form
|
||||
register_rest_route($namespace, '/countries', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ new self(), 'get_countries' ],
|
||||
'callback' => [new self(), 'get_countries'],
|
||||
'permission_callback' => '__return_true', // Public - needed for checkout
|
||||
]);
|
||||
// Public order view endpoint for thank you page
|
||||
register_rest_route($namespace, '/checkout/order/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ new self(), 'get_order' ],
|
||||
'callback' => [new self(), 'get_order'],
|
||||
'permission_callback' => '__return_true', // Public, validated via order_key
|
||||
'args' => [
|
||||
'key' => [
|
||||
@@ -53,8 +58,14 @@ class CheckoutController {
|
||||
// Get available shipping rates for given address
|
||||
register_rest_route($namespace, '/checkout/shipping-rates', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ new self(), 'get_shipping_rates' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ],
|
||||
'callback' => [new self(), 'get_shipping_rates'],
|
||||
'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'],
|
||||
]);
|
||||
// Process payment for an existing order (e.g. renewal)
|
||||
register_rest_route($namespace, '/checkout/pay-order/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [new self(), 'pay_order'],
|
||||
'permission_callback' => '__return_true', // Validated via order key/owner in method
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -68,7 +79,8 @@ class CheckoutController {
|
||||
* shipping_method: "flat_rate:1" | "free_shipping:3" | ...
|
||||
* }
|
||||
*/
|
||||
public function quote(WP_REST_Request $r): array {
|
||||
public function quote(WP_REST_Request $r): array
|
||||
{
|
||||
$__t0 = microtime(true);
|
||||
$payload = $this->sanitize_payload($r);
|
||||
|
||||
@@ -162,7 +174,8 @@ class CheckoutController {
|
||||
* Validates access via order_key (for guests) or logged-in customer ID
|
||||
* GET /checkout/order/{id}?key=wc_order_xxx
|
||||
*/
|
||||
public function get_order(WP_REST_Request $r): array {
|
||||
public function get_order(WP_REST_Request $r): array
|
||||
{
|
||||
$order_id = absint($r['id']);
|
||||
$order_key = sanitize_text_field($r->get_param('key') ?? '');
|
||||
|
||||
@@ -175,9 +188,12 @@ class CheckoutController {
|
||||
return ['error' => __('Order not found', 'woonoow')];
|
||||
}
|
||||
|
||||
// Validate access: order_key must match OR user must be logged in and own the order
|
||||
// Validate access: order_key must match OR user must be logged in and own the order (or be admin)
|
||||
$valid_key = $order_key && hash_equals($order->get_order_key(), $order_key);
|
||||
$valid_owner = is_user_logged_in() && get_current_user_id() === $order->get_customer_id();
|
||||
$valid_owner = is_user_logged_in() && (
|
||||
get_current_user_id() === $order->get_customer_id() ||
|
||||
current_user_can('manage_woocommerce')
|
||||
);
|
||||
|
||||
if (!$valid_key && !$valid_owner) {
|
||||
return ['error' => __('Unauthorized access to order', 'woonoow')];
|
||||
@@ -197,7 +213,7 @@ class CheckoutController {
|
||||
'image' => $product ? wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Build shipping lines
|
||||
$shipping_lines = [];
|
||||
foreach ($order->get_shipping_methods() as $shipping_item) {
|
||||
@@ -208,16 +224,16 @@ class CheckoutController {
|
||||
'total' => wc_price($shipping_item->get_total()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Get tracking info from order meta (various plugins use different keys)
|
||||
$tracking_number = $order->get_meta('_tracking_number')
|
||||
?: $order->get_meta('_wc_shipment_tracking_items')
|
||||
$tracking_number = $order->get_meta('_tracking_number')
|
||||
?: $order->get_meta('_wc_shipment_tracking_items')
|
||||
?: $order->get_meta('_rajaongkir_awb_number')
|
||||
?: '';
|
||||
$tracking_url = $order->get_meta('_tracking_url')
|
||||
$tracking_url = $order->get_meta('_tracking_url')
|
||||
?: $order->get_meta('_rajaongkir_tracking_url')
|
||||
?: '';
|
||||
|
||||
|
||||
// Check for shipment tracking plugin format (array of tracking items)
|
||||
if (is_array($tracking_number) && !empty($tracking_number)) {
|
||||
$first_tracking = reset($tracking_number);
|
||||
@@ -230,6 +246,7 @@ class CheckoutController {
|
||||
'id' => $order->get_id(),
|
||||
'number' => $order->get_order_number(),
|
||||
'status' => $order->get_status(),
|
||||
'created_via' => $order->get_created_via(),
|
||||
'subtotal' => (float) $order->get_subtotal(),
|
||||
'discount_total' => (float) $order->get_discount_total(),
|
||||
'shipping_total' => (float) $order->get_shipping_total(),
|
||||
@@ -249,6 +266,28 @@ class CheckoutController {
|
||||
'phone' => $order->get_billing_phone(),
|
||||
],
|
||||
'items' => $items,
|
||||
'subscription' => $this->get_subscription_for_response($order),
|
||||
'available_gateways' => $this->get_available_gateways_for_order($order),
|
||||
];
|
||||
}
|
||||
|
||||
private function get_subscription_for_response($order)
|
||||
{
|
||||
if (!class_exists('\WooNooW\Modules\Subscription\SubscriptionManager')) {
|
||||
return null;
|
||||
}
|
||||
$sub = \WooNooW\Modules\Subscription\SubscriptionManager::get_by_order_id($order->get_id());
|
||||
if (!$sub) return null;
|
||||
|
||||
return [
|
||||
'id' => (int) $sub->id,
|
||||
'status' => $sub->status,
|
||||
'billing_period' => $sub->billing_period,
|
||||
'billing_interval' => (int) $sub->billing_interval,
|
||||
'start_date' => $sub->start_date,
|
||||
'next_payment_date' => $sub->next_payment_date,
|
||||
'end_date' => $sub->end_date,
|
||||
'recurring_amount' => (float) $sub->recurring_amount,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -263,7 +302,8 @@ class CheckoutController {
|
||||
* payment_method: "cod" | "bacs" | ...
|
||||
* }
|
||||
*/
|
||||
public function submit(WP_REST_Request $r): array {
|
||||
public function submit(WP_REST_Request $r): array
|
||||
{
|
||||
$__t0 = microtime(true);
|
||||
$payload = $this->sanitize_payload($r);
|
||||
|
||||
@@ -281,11 +321,11 @@ class CheckoutController {
|
||||
if (is_user_logged_in()) {
|
||||
$user_id = get_current_user_id();
|
||||
$order->set_customer_id($user_id);
|
||||
|
||||
|
||||
// Update user's billing information from checkout data
|
||||
if (!empty($payload['billing'])) {
|
||||
$billing = $payload['billing'];
|
||||
|
||||
|
||||
// Update first name and last name
|
||||
if (!empty($billing['first_name'])) {
|
||||
update_user_meta($user_id, 'first_name', sanitize_text_field($billing['first_name']));
|
||||
@@ -295,12 +335,12 @@ class CheckoutController {
|
||||
update_user_meta($user_id, 'last_name', sanitize_text_field($billing['last_name']));
|
||||
update_user_meta($user_id, 'billing_last_name', sanitize_text_field($billing['last_name']));
|
||||
}
|
||||
|
||||
|
||||
// Update billing phone
|
||||
if (!empty($billing['phone'])) {
|
||||
update_user_meta($user_id, 'billing_phone', sanitize_text_field($billing['phone']));
|
||||
}
|
||||
|
||||
|
||||
// Update billing email
|
||||
if (!empty($billing['email'])) {
|
||||
update_user_meta($user_id, 'billing_email', sanitize_email($billing['email']));
|
||||
@@ -310,20 +350,20 @@ class CheckoutController {
|
||||
// Guest checkout - check if auto-register is enabled
|
||||
$customer_settings = \WooNooW\Compat\CustomerSettingsProvider::get_settings();
|
||||
$auto_register = $customer_settings['auto_register_members'] ?? false;
|
||||
|
||||
|
||||
if ($auto_register && !empty($payload['billing']['email'])) {
|
||||
$email = sanitize_email($payload['billing']['email']);
|
||||
|
||||
|
||||
// Check if user already exists
|
||||
$existing_user = get_user_by('email', $email);
|
||||
|
||||
|
||||
if ($existing_user) {
|
||||
// User exists - link order to them
|
||||
$order->set_customer_id($existing_user->ID);
|
||||
} else {
|
||||
// Create new user account
|
||||
$password = wp_generate_password(12, true, true);
|
||||
|
||||
|
||||
$userdata = [
|
||||
'user_login' => $email,
|
||||
'user_email' => $email,
|
||||
@@ -333,24 +373,24 @@ class CheckoutController {
|
||||
'display_name' => trim((sanitize_text_field($payload['billing']['first_name'] ?? '') . ' ' . sanitize_text_field($payload['billing']['last_name'] ?? ''))) ?: $email,
|
||||
'role' => 'customer', // WooCommerce customer role
|
||||
];
|
||||
|
||||
|
||||
$new_user_id = wp_insert_user($userdata);
|
||||
|
||||
|
||||
if (!is_wp_error($new_user_id)) {
|
||||
// Link order to new user
|
||||
$order->set_customer_id($new_user_id);
|
||||
|
||||
|
||||
// Store temp password in user meta for email template
|
||||
// The real password is already set via wp_insert_user
|
||||
update_user_meta($new_user_id, '_woonoow_temp_password', $password);
|
||||
|
||||
|
||||
// AUTO-LOGIN: Set authentication cookie so user is logged in after page reload
|
||||
wp_set_auth_cookie($new_user_id, true);
|
||||
wp_set_current_user($new_user_id);
|
||||
|
||||
|
||||
// Set WooCommerce customer billing data
|
||||
$customer = new \WC_Customer($new_user_id);
|
||||
|
||||
|
||||
if (!empty($payload['billing']['first_name'])) $customer->set_billing_first_name(sanitize_text_field($payload['billing']['first_name']));
|
||||
if (!empty($payload['billing']['last_name'])) $customer->set_billing_last_name(sanitize_text_field($payload['billing']['last_name']));
|
||||
if (!empty($payload['billing']['email'])) $customer->set_billing_email(sanitize_email($payload['billing']['email']));
|
||||
@@ -360,9 +400,9 @@ class CheckoutController {
|
||||
if (!empty($payload['billing']['state'])) $customer->set_billing_state(sanitize_text_field($payload['billing']['state']));
|
||||
if (!empty($payload['billing']['postcode'])) $customer->set_billing_postcode(sanitize_text_field($payload['billing']['postcode']));
|
||||
if (!empty($payload['billing']['country'])) $customer->set_billing_country(sanitize_text_field($payload['billing']['country']));
|
||||
|
||||
|
||||
$customer->save();
|
||||
|
||||
|
||||
// Send new account email (WooCommerce will handle this automatically via hook)
|
||||
do_action('woocommerce_created_customer', $new_user_id, $userdata, $password);
|
||||
}
|
||||
@@ -430,12 +470,12 @@ class CheckoutController {
|
||||
// Fallback: use shipping_cost directly from frontend
|
||||
// This handles API-based shipping like Rajaongkir where WC zones don't apply
|
||||
$item = new \WC_Order_Item_Shipping();
|
||||
|
||||
|
||||
// Parse method ID from shipping_method (format: "method_id:instance_id" or "method_id:instance_id:variant")
|
||||
$parts = explode(':', $payload['shipping_method']);
|
||||
$method_id = $parts[0] ?? 'shipping';
|
||||
$instance_id = isset($parts[1]) ? (int)$parts[1] : 0;
|
||||
|
||||
|
||||
$item->set_props([
|
||||
'method_title' => sanitize_text_field($payload['shipping_title'] ?? 'Shipping'),
|
||||
'method_id' => sanitize_text_field($method_id),
|
||||
@@ -479,12 +519,73 @@ class CheckoutController {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process payment for an existing order
|
||||
* POST /checkout/pay-order/{id}
|
||||
*/
|
||||
public function pay_order(WP_REST_Request $r): array
|
||||
{
|
||||
$order_id = absint($r['id']);
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (!$order) {
|
||||
return ['error' => __('Order not found', 'woonoow')];
|
||||
}
|
||||
|
||||
// Validate access
|
||||
$key = $r->get_param('key'); // optional if logged in
|
||||
$valid_key = $key && hash_equals($order->get_order_key(), $key);
|
||||
$valid_owner = is_user_logged_in() && (
|
||||
get_current_user_id() === $order->get_customer_id() ||
|
||||
current_user_can('manage_woocommerce')
|
||||
);
|
||||
|
||||
if (!$valid_key && !$valid_owner) {
|
||||
return ['error' => __('Unauthorized access', 'woonoow')];
|
||||
}
|
||||
|
||||
if ($order->is_paid()) {
|
||||
return ['error' => __('Order already paid', 'woonoow')];
|
||||
}
|
||||
|
||||
$payment_method = wc_clean($r->get_param('payment_method'));
|
||||
if (empty($payment_method)) {
|
||||
return ['error' => __('Payment method required', 'woonoow')];
|
||||
}
|
||||
|
||||
// Update payment method
|
||||
$available = WC()->payment_gateways()->get_available_payment_gateways();
|
||||
if (!isset($available[$payment_method])) {
|
||||
return ['error' => __('Invalid payment method', 'woonoow')];
|
||||
}
|
||||
|
||||
$gateway = $available[$payment_method];
|
||||
$order->set_payment_method($gateway);
|
||||
$order->save();
|
||||
|
||||
// Process payment
|
||||
$result = $gateway->process_payment($order_id);
|
||||
|
||||
if (isset($result['result']) && $result['result'] === 'success') {
|
||||
return [
|
||||
'ok' => true,
|
||||
'redirect' => $result['redirect'] ?? $order->get_checkout_order_received_url(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => __('Payment failed', 'woonoow') . (isset($result['result']) ? ': ' . $result['result'] : ''),
|
||||
'messages' => wc_get_notices('error'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checkout fields with all filters applied
|
||||
* Accepts: { items: [...], is_digital_only?: bool }
|
||||
* Returns fields with required, hidden, etc. based on addons + cart context
|
||||
*/
|
||||
public function get_fields(WP_REST_Request $r): array {
|
||||
public function get_fields(WP_REST_Request $r): array
|
||||
{
|
||||
$json = $r->get_json_params();
|
||||
$items = isset($json['items']) && is_array($json['items']) ? $json['items'] : [];
|
||||
$is_digital_only = isset($json['is_digital_only']) ? (bool) $json['is_digital_only'] : false;
|
||||
@@ -504,7 +605,7 @@ class CheckoutController {
|
||||
foreach ($fieldset as $key => $field) {
|
||||
// Check if field should be hidden
|
||||
$hidden = false;
|
||||
|
||||
|
||||
// Hide shipping fields if digital only (your existing logic)
|
||||
if ($is_digital_only && $fieldset_key === 'shipping') {
|
||||
$hidden = true;
|
||||
@@ -534,7 +635,7 @@ class CheckoutController {
|
||||
'priority' => $field['priority'] ?? 10,
|
||||
'options' => $field['options'] ?? null, // For select fields
|
||||
'custom' => !in_array($key, $this->get_standard_field_keys()), // Flag custom fields
|
||||
'autocomplete'=> $field['autocomplete'] ?? '',
|
||||
'autocomplete' => $field['autocomplete'] ?? '',
|
||||
'validate' => $field['validate'] ?? [],
|
||||
// New fields for dynamic rendering
|
||||
'input_class' => $field['input_class'] ?? [],
|
||||
@@ -549,7 +650,7 @@ class CheckoutController {
|
||||
}
|
||||
|
||||
// Sort by priority
|
||||
usort($formatted, function($a, $b) {
|
||||
usort($formatted, function ($a, $b) {
|
||||
return $a['priority'] <=> $b['priority'];
|
||||
});
|
||||
|
||||
@@ -564,7 +665,8 @@ class CheckoutController {
|
||||
* Get list of standard WooCommerce field keys
|
||||
* Plugins can extend this list via the 'woonoow_standard_checkout_field_keys' filter
|
||||
*/
|
||||
private function get_standard_field_keys(): array {
|
||||
private function get_standard_field_keys(): array
|
||||
{
|
||||
$keys = [
|
||||
'billing_first_name',
|
||||
'billing_last_name',
|
||||
@@ -588,7 +690,7 @@ class CheckoutController {
|
||||
'shipping_postcode',
|
||||
'order_comments',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Filter the list of standard checkout field keys.
|
||||
* Plugins can add their own field keys to be recognized as "standard" (not custom).
|
||||
@@ -600,14 +702,19 @@ class CheckoutController {
|
||||
|
||||
/** ----------------- Helpers ----------------- **/
|
||||
|
||||
private function accurate_quote_via_wc_cart(array $payload): array {
|
||||
if (!WC()->customer) { WC()->customer = new \WC_Customer(get_current_user_id(), true); }
|
||||
if (!WC()->cart) { WC()->cart = new \WC_Cart(); }
|
||||
private function accurate_quote_via_wc_cart(array $payload): array
|
||||
{
|
||||
if (!WC()->customer) {
|
||||
WC()->customer = new \WC_Customer(get_current_user_id(), true);
|
||||
}
|
||||
if (!WC()->cart) {
|
||||
WC()->cart = new \WC_Cart();
|
||||
}
|
||||
|
||||
// Address context for taxes/shipping rules - set temporarily without saving to user profile
|
||||
$ship = !empty($payload['shipping']) ? $payload['shipping'] : $payload['billing'];
|
||||
if (!empty($payload['billing'])) {
|
||||
foreach (['country','state','postcode','city','address_1','address_2'] as $k) {
|
||||
foreach (['country', 'state', 'postcode', 'city', 'address_1', 'address_2'] as $k) {
|
||||
$setter = 'set_billing_' . $k;
|
||||
if (method_exists(WC()->customer, $setter) && isset($payload['billing'][$k])) {
|
||||
WC()->customer->{$setter}(wc_clean($payload['billing'][$k]));
|
||||
@@ -615,7 +722,7 @@ class CheckoutController {
|
||||
}
|
||||
}
|
||||
if (!empty($ship)) {
|
||||
foreach (['country','state','postcode','city','address_1','address_2'] as $k) {
|
||||
foreach (['country', 'state', 'postcode', 'city', 'address_1', 'address_2'] as $k) {
|
||||
$setter = 'set_shipping_' . $k;
|
||||
if (method_exists(WC()->customer, $setter) && isset($ship[$k])) {
|
||||
WC()->customer->{$setter}(wc_clean($ship[$k]));
|
||||
@@ -685,7 +792,8 @@ class CheckoutController {
|
||||
];
|
||||
}
|
||||
|
||||
private function sanitize_payload(WP_REST_Request $r): array {
|
||||
private function sanitize_payload(WP_REST_Request $r): array
|
||||
{
|
||||
$json = $r->get_json_params();
|
||||
|
||||
$items = isset($json['items']) && is_array($json['items']) ? $json['items'] : [];
|
||||
@@ -704,7 +812,7 @@ class CheckoutController {
|
||||
];
|
||||
}, $items),
|
||||
'billing' => $billing,
|
||||
'shipping'=> $shipping,
|
||||
'shipping' => $shipping,
|
||||
'coupons' => $coupons,
|
||||
'shipping_method' => isset($json['shipping_method']) ? wc_clean($json['shipping_method']) : null,
|
||||
'payment_method' => isset($json['payment_method']) ? wc_clean($json['payment_method']) : null,
|
||||
@@ -716,7 +824,8 @@ class CheckoutController {
|
||||
];
|
||||
}
|
||||
|
||||
private function load_product(array $line) {
|
||||
private function load_product(array $line)
|
||||
{
|
||||
$pid = (int)($line['variation_id'] ?? 0) ?: (int)($line['product_id'] ?? 0);
|
||||
if (!$pid) {
|
||||
return new WP_Error('bad_item', __('Invalid product id', 'woonoow'));
|
||||
@@ -728,8 +837,9 @@ class CheckoutController {
|
||||
return $product;
|
||||
}
|
||||
|
||||
private function only_address_fields(array $src): array {
|
||||
$keys = ['first_name','last_name','company','address_1','address_2','city','state','postcode','country','email','phone'];
|
||||
private function only_address_fields(array $src): array
|
||||
{
|
||||
$keys = ['first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'email', 'phone'];
|
||||
$out = [];
|
||||
foreach ($keys as $k) {
|
||||
if (isset($src[$k])) $out[$k] = wc_clean(wp_unslash($src[$k]));
|
||||
@@ -737,7 +847,8 @@ class CheckoutController {
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function estimate_shipping(array $address, ?string $chosen_method): float {
|
||||
private function estimate_shipping(array $address, ?string $chosen_method): float
|
||||
{
|
||||
$country = wc_clean($address['country'] ?? '');
|
||||
$postcode = wc_clean($address['postcode'] ?? '');
|
||||
$state = wc_clean($address['state'] ?? '');
|
||||
@@ -745,12 +856,14 @@ class CheckoutController {
|
||||
|
||||
$cache_key = 'wnw_ship_' . md5(json_encode([$country, $state, $postcode, $city, (string) $chosen_method]));
|
||||
$cached = wp_cache_get($cache_key, 'woonoow');
|
||||
if ($cached !== false) { return (float) $cached; }
|
||||
if ($cached !== false) {
|
||||
return (float) $cached;
|
||||
}
|
||||
|
||||
if (!$country) return 0.0;
|
||||
|
||||
$packages = [[
|
||||
'destination' => compact('country','state','postcode','city'),
|
||||
'destination' => compact('country', 'state', 'postcode', 'city'),
|
||||
'contents_cost' => 0, // not exact in v0
|
||||
'contents' => [],
|
||||
'applied_coupons' => [],
|
||||
@@ -778,7 +891,8 @@ class CheckoutController {
|
||||
return $cost;
|
||||
}
|
||||
|
||||
private function find_shipping_rate_for_order(WC_Order $order, string $chosen) {
|
||||
private function find_shipping_rate_for_order(WC_Order $order, string $chosen)
|
||||
{
|
||||
$shipping = $order->get_address('shipping');
|
||||
$packages = [[
|
||||
'destination' => [
|
||||
@@ -810,12 +924,13 @@ class CheckoutController {
|
||||
* Get countries and states for checkout form
|
||||
* Public endpoint - no authentication required
|
||||
*/
|
||||
public function get_countries(): array {
|
||||
public function get_countries(): array
|
||||
{
|
||||
$wc_countries = WC()->countries;
|
||||
|
||||
|
||||
// Get allowed selling countries
|
||||
$allowed = $wc_countries->get_allowed_countries();
|
||||
|
||||
|
||||
// Format for frontend
|
||||
$countries = [];
|
||||
foreach ($allowed as $code => $name) {
|
||||
@@ -824,7 +939,7 @@ class CheckoutController {
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Get states for all allowed countries
|
||||
$states = [];
|
||||
foreach (array_keys($allowed) as $country_code) {
|
||||
@@ -833,10 +948,10 @@ class CheckoutController {
|
||||
$states[$country_code] = $country_states;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get default country
|
||||
$default_country = $wc_countries->get_base_country();
|
||||
|
||||
|
||||
return [
|
||||
'countries' => $countries,
|
||||
'states' => $states,
|
||||
@@ -849,16 +964,17 @@ class CheckoutController {
|
||||
* POST /checkout/shipping-rates
|
||||
* Body: { shipping: { country, state, city, postcode, destination_id? }, items: [...] }
|
||||
*/
|
||||
public function get_shipping_rates(WP_REST_Request $r): array {
|
||||
public function get_shipping_rates(WP_REST_Request $r): array
|
||||
{
|
||||
$payload = $r->get_json_params();
|
||||
$shipping = $payload['shipping'] ?? [];
|
||||
$items = $payload['items'] ?? [];
|
||||
|
||||
|
||||
$country = wc_clean($shipping['country'] ?? '');
|
||||
$state = wc_clean($shipping['state'] ?? '');
|
||||
$city = wc_clean($shipping['city'] ?? '');
|
||||
$postcode = wc_clean($shipping['postcode'] ?? '');
|
||||
|
||||
|
||||
if (empty($country)) {
|
||||
return [
|
||||
'ok' => true,
|
||||
@@ -866,10 +982,10 @@ class CheckoutController {
|
||||
'message' => 'Country is required',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Trigger hook for plugins to set session data (e.g., Rajaongkir destination_id)
|
||||
do_action('woonoow/shipping/before_calculate', $shipping, $items);
|
||||
|
||||
|
||||
// Set customer location for shipping calculation
|
||||
if (WC()->customer) {
|
||||
WC()->customer->set_shipping_country($country);
|
||||
@@ -877,7 +993,7 @@ class CheckoutController {
|
||||
WC()->customer->set_shipping_city($city);
|
||||
WC()->customer->set_shipping_postcode($postcode);
|
||||
}
|
||||
|
||||
|
||||
// Build package for shipping calculation
|
||||
$contents = [];
|
||||
$contents_cost = 0;
|
||||
@@ -893,7 +1009,7 @@ class CheckoutController {
|
||||
];
|
||||
$contents_cost += $price * $qty;
|
||||
}
|
||||
|
||||
|
||||
$package = [
|
||||
'destination' => [
|
||||
'country' => $country,
|
||||
@@ -906,7 +1022,7 @@ class CheckoutController {
|
||||
'applied_coupons' => [],
|
||||
'user' => ['ID' => get_current_user_id()],
|
||||
];
|
||||
|
||||
|
||||
// Get matching shipping zone
|
||||
$zone = WC_Shipping_Zones::get_zone_matching_package($package);
|
||||
if (!$zone) {
|
||||
@@ -916,11 +1032,11 @@ class CheckoutController {
|
||||
'message' => 'No shipping zone matches your location',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Get enabled shipping methods from zone
|
||||
$methods = $zone->get_shipping_methods(true);
|
||||
$rates = [];
|
||||
|
||||
|
||||
foreach ($methods as $method) {
|
||||
// Check if method has rates (some methods like live rate need to calculate)
|
||||
if (method_exists($method, 'get_rates_for_package')) {
|
||||
@@ -938,14 +1054,14 @@ class CheckoutController {
|
||||
// Fallback for simple methods
|
||||
$method_id = $method->id . ':' . $method->get_instance_id();
|
||||
$cost = 0;
|
||||
|
||||
|
||||
// Try to get cost from method
|
||||
if (isset($method->cost)) {
|
||||
$cost = (float) $method->cost;
|
||||
} elseif (method_exists($method, 'get_option')) {
|
||||
$cost = (float) $method->get_option('cost', 0);
|
||||
}
|
||||
|
||||
|
||||
$rates[] = [
|
||||
'id' => $method_id,
|
||||
'label' => $method->get_title(),
|
||||
@@ -955,11 +1071,34 @@ class CheckoutController {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'rates' => $rates,
|
||||
'zone_name' => $zone->get_zone_name(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function get_available_gateways_for_order(WC_Order $order): array
|
||||
{
|
||||
// Mock cart for gateways that check cart total
|
||||
if (!WC()->cart) {
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
// We can't easily bake the order into the cart, but many gateways just check 'needs_payment'
|
||||
// or country.
|
||||
|
||||
$gateways = WC()->payment_gateways()->get_available_payment_gateways();
|
||||
$results = [];
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
$results[] = [
|
||||
'id' => $gateway->id,
|
||||
'title' => $gateway->get_title() ?: $gateway->method_title ?: ucfirst($gateway->id), // Fallbacks
|
||||
'description' => $gateway->get_description(),
|
||||
'icon' => $gateway->get_icon(),
|
||||
];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +424,23 @@ class ProductsController {
|
||||
update_post_meta($product->get_id(), '_woonoow_license_expiry_days', self::sanitize_number($data['license_duration_days']));
|
||||
}
|
||||
|
||||
// Subscription meta
|
||||
if (isset($data['subscription_enabled'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_enabled', $data['subscription_enabled'] ? 'yes' : 'no');
|
||||
}
|
||||
if (isset($data['subscription_period'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_period', sanitize_key($data['subscription_period']));
|
||||
}
|
||||
if (isset($data['subscription_interval'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_interval', absint($data['subscription_interval']));
|
||||
}
|
||||
if (isset($data['subscription_trial_days'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_trial_days', absint($data['subscription_trial_days']));
|
||||
}
|
||||
if (isset($data['subscription_signup_fee'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_signup_fee', self::sanitize_number($data['subscription_signup_fee']));
|
||||
}
|
||||
|
||||
// Handle variations for variable products
|
||||
if ($type === 'variable' && !empty($data['attributes']) && is_array($data['attributes'])) {
|
||||
self::save_product_attributes($product, $data['attributes']);
|
||||
@@ -568,6 +585,23 @@ class ProductsController {
|
||||
update_post_meta($product->get_id(), '_woonoow_license_expiry_days', self::sanitize_number($data['license_duration_days']));
|
||||
}
|
||||
|
||||
// Subscription meta
|
||||
if (isset($data['subscription_enabled'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_enabled', $data['subscription_enabled'] ? 'yes' : 'no');
|
||||
}
|
||||
if (isset($data['subscription_period'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_period', sanitize_key($data['subscription_period']));
|
||||
}
|
||||
if (isset($data['subscription_interval'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_interval', absint($data['subscription_interval']));
|
||||
}
|
||||
if (isset($data['subscription_trial_days'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_trial_days', absint($data['subscription_trial_days']));
|
||||
}
|
||||
if (isset($data['subscription_signup_fee'])) {
|
||||
update_post_meta($product->get_id(), '_woonoow_subscription_signup_fee', self::sanitize_number($data['subscription_signup_fee']));
|
||||
}
|
||||
|
||||
// Allow plugins to perform additional updates (Level 1 compatibility)
|
||||
do_action('woonoow/product_updated', $product, $data, $request);
|
||||
|
||||
@@ -767,6 +801,13 @@ class ProductsController {
|
||||
$data['license_activation_limit'] = get_post_meta($product->get_id(), '_license_activation_limit', true) ?: '';
|
||||
$data['license_duration_days'] = get_post_meta($product->get_id(), '_license_duration_days', true) ?: '';
|
||||
|
||||
// Subscription fields
|
||||
$data['subscription_enabled'] = get_post_meta($product->get_id(), '_woonoow_subscription_enabled', true) === 'yes';
|
||||
$data['subscription_period'] = get_post_meta($product->get_id(), '_woonoow_subscription_period', true) ?: 'month';
|
||||
$data['subscription_interval'] = get_post_meta($product->get_id(), '_woonoow_subscription_interval', true) ?: '1';
|
||||
$data['subscription_trial_days'] = get_post_meta($product->get_id(), '_woonoow_subscription_trial_days', true) ?: '';
|
||||
$data['subscription_signup_fee'] = get_post_meta($product->get_id(), '_woonoow_subscription_signup_fee', true) ?: '';
|
||||
|
||||
// Images array (URLs) for frontend - featured + gallery
|
||||
$images = [];
|
||||
$featured_image_id = $product->get_image_id();
|
||||
|
||||
@@ -26,6 +26,7 @@ use WooNooW\Api\ModuleSettingsController;
|
||||
use WooNooW\Api\CampaignsController;
|
||||
use WooNooW\Api\DocsController;
|
||||
use WooNooW\Api\LicensesController;
|
||||
use WooNooW\Api\SubscriptionsController;
|
||||
use WooNooW\Frontend\ShopController;
|
||||
use WooNooW\Frontend\CartController as FrontendCartController;
|
||||
use WooNooW\Frontend\AccountController;
|
||||
@@ -163,6 +164,9 @@ class Routes {
|
||||
// Licenses controller (licensing module)
|
||||
LicensesController::register_routes();
|
||||
|
||||
// Subscriptions controller (subscription module)
|
||||
SubscriptionsController::register_routes();
|
||||
|
||||
// Modules controller
|
||||
$modules_controller = new ModulesController();
|
||||
$modules_controller->register_routes();
|
||||
|
||||
476
includes/Api/SubscriptionsController.php
Normal file
476
includes/Api/SubscriptionsController.php
Normal file
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Subscriptions API Controller
|
||||
*
|
||||
* REST API endpoints for subscription management.
|
||||
*
|
||||
* @package WooNooW\Api
|
||||
*/
|
||||
|
||||
namespace WooNooW\Api;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
use WooNooW\Modules\Subscription\SubscriptionManager;
|
||||
|
||||
class SubscriptionsController
|
||||
{
|
||||
|
||||
/**
|
||||
* Register REST routes
|
||||
*/
|
||||
public static function register_routes()
|
||||
{
|
||||
// Check if module is enabled
|
||||
if (!ModuleRegistry::is_enabled('subscription')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Admin routes
|
||||
register_rest_route('woonoow/v1', '/subscriptions', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_subscriptions'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)', [
|
||||
'methods' => 'PUT',
|
||||
'callback' => [__CLASS__, 'update_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)/cancel', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'cancel_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)/renew', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'renew_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)/pause', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'pause_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/subscriptions/(?P<id>\d+)/resume', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'resume_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_woocommerce');
|
||||
},
|
||||
]);
|
||||
|
||||
// Customer routes
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_customer_subscriptions'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_customer_subscription'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions/(?P<id>\d+)/cancel', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_cancel'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions/(?P<id>\d+)/pause', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_pause'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions/(?P<id>\d+)/resume', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_resume'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('woonoow/v1', '/account/subscriptions/(?P<id>\d+)/renew', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'customer_renew'],
|
||||
'permission_callback' => function () {
|
||||
return is_user_logged_in();
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all subscriptions (admin)
|
||||
*/
|
||||
public static function get_subscriptions(WP_REST_Request $request)
|
||||
{
|
||||
$args = [
|
||||
'status' => $request->get_param('status'),
|
||||
'product_id' => $request->get_param('product_id'),
|
||||
'user_id' => $request->get_param('user_id'),
|
||||
'limit' => $request->get_param('per_page') ?: 20,
|
||||
'offset' => (($request->get_param('page') ?: 1) - 1) * ($request->get_param('per_page') ?: 20),
|
||||
];
|
||||
|
||||
$subscriptions = SubscriptionManager::get_all($args);
|
||||
$total = SubscriptionManager::count(['status' => $args['status']]);
|
||||
|
||||
// Enrich with product and user info
|
||||
$enriched = [];
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$enriched[] = self::enrich_subscription($subscription);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'subscriptions' => $enriched,
|
||||
'total' => $total,
|
||||
'page' => $request->get_param('page') ?: 1,
|
||||
'per_page' => $args['limit'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single subscription (admin)
|
||||
*/
|
||||
public static function get_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$enriched = self::enrich_subscription($subscription);
|
||||
$enriched['orders'] = SubscriptionManager::get_orders($subscription->id);
|
||||
|
||||
return new WP_REST_Response($enriched);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subscription (admin)
|
||||
*/
|
||||
public static function update_subscription(WP_REST_Request $request)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$data = $request->get_json_params();
|
||||
$allowed_fields = ['status', 'next_payment_date', 'end_date', 'billing_period', 'billing_interval'];
|
||||
|
||||
$update_data = [];
|
||||
$format = [];
|
||||
|
||||
foreach ($allowed_fields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$update_data[$field] = $data[$field];
|
||||
$format[] = is_numeric($data[$field]) ? '%d' : '%s';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($update_data)) {
|
||||
return new WP_Error('no_data', __('No valid fields to update', 'woonoow'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$table = $wpdb->prefix . 'woonoow_subscriptions';
|
||||
$updated = $wpdb->update($table, $update_data, ['id' => $subscription->id], $format, ['%d']);
|
||||
|
||||
if ($updated === false) {
|
||||
return new WP_Error('update_failed', __('Failed to update subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel subscription (admin)
|
||||
*/
|
||||
public static function cancel_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$data = $request->get_json_params();
|
||||
$reason = $data['reason'] ?? 'Cancelled by admin';
|
||||
|
||||
$result = SubscriptionManager::cancel($request->get_param('id'), $reason);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('cancel_failed', __('Failed to cancel subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renew subscription (admin - force immediate renewal)
|
||||
*/
|
||||
public static function renew_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$result = SubscriptionManager::renew($request->get_param('id'));
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('renew_failed', __('Failed to process renewal', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true, 'order_id' => $result['order_id']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause subscription (admin)
|
||||
*/
|
||||
public static function pause_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$result = SubscriptionManager::pause($request->get_param('id'));
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('pause_failed', __('Failed to pause subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume subscription (admin)
|
||||
*/
|
||||
public static function resume_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$result = SubscriptionManager::resume($request->get_param('id'));
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('resume_failed', __('Failed to resume subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer's subscriptions
|
||||
*/
|
||||
public static function get_customer_subscriptions(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$subscriptions = SubscriptionManager::get_by_user($user_id, [
|
||||
'status' => $request->get_param('status'),
|
||||
'limit' => $request->get_param('per_page') ?: 20,
|
||||
'offset' => (($request->get_param('page') ?: 1) - 1) * ($request->get_param('per_page') ?: 20),
|
||||
]);
|
||||
|
||||
// Enrich each subscription
|
||||
$enriched = [];
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$enriched[] = self::enrich_subscription($subscription);
|
||||
}
|
||||
|
||||
return new WP_REST_Response($enriched);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer's subscription detail
|
||||
*/
|
||||
public static function get_customer_subscription(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription || $subscription->user_id != $user_id) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$enriched = self::enrich_subscription($subscription);
|
||||
$enriched['orders'] = SubscriptionManager::get_orders($subscription->id);
|
||||
|
||||
return new WP_REST_Response($enriched);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer cancel their own subscription
|
||||
*/
|
||||
public static function customer_cancel(WP_REST_Request $request)
|
||||
{
|
||||
// Check if customer cancellation is allowed
|
||||
$settings = ModuleRegistry::get_settings('subscription');
|
||||
if (empty($settings['allow_customer_cancel'])) {
|
||||
return new WP_Error('not_allowed', __('Customer cancellation is not allowed', 'woonoow'), ['status' => 403]);
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription || $subscription->user_id != $user_id) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$data = $request->get_json_params();
|
||||
$reason = $data['reason'] ?? 'Cancelled by customer';
|
||||
|
||||
$result = SubscriptionManager::cancel($subscription->id, $reason);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('cancel_failed', __('Failed to cancel subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer pause their own subscription
|
||||
*/
|
||||
public static function customer_pause(WP_REST_Request $request)
|
||||
{
|
||||
// Check if customer pause is allowed
|
||||
$settings = ModuleRegistry::get_settings('subscription');
|
||||
if (empty($settings['allow_customer_pause'])) {
|
||||
return new WP_Error('not_allowed', __('Customer pause is not allowed', 'woonoow'), ['status' => 403]);
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription || $subscription->user_id != $user_id) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$result = SubscriptionManager::pause($subscription->id);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('pause_failed', __('Failed to pause subscription. Maximum pauses may have been reached.', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer resume their own subscription
|
||||
*/
|
||||
public static function customer_resume(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription || $subscription->user_id != $user_id) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$result = SubscriptionManager::resume($subscription->id);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_Error('resume_failed', __('Failed to resume subscription', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer renew their own subscription (Early Renewal)
|
||||
*/
|
||||
public static function customer_renew(WP_REST_Request $request)
|
||||
{
|
||||
$user_id = get_current_user_id();
|
||||
$subscription = SubscriptionManager::get($request->get_param('id'));
|
||||
|
||||
if (!$subscription || $subscription->user_id != $user_id) {
|
||||
return new WP_Error('not_found', __('Subscription not found', 'woonoow'), ['status' => 404]);
|
||||
}
|
||||
|
||||
// Check if subscription is active (for early renewal) or on-hold with no pending payment
|
||||
if ($subscription->status !== 'active' && $subscription->status !== 'on-hold') {
|
||||
return new WP_Error('not_allowed', __('Only active subscriptions can be renewed early', 'woonoow'), ['status' => 403]);
|
||||
}
|
||||
|
||||
// Trigger renewal
|
||||
$result = SubscriptionManager::renew($subscription->id);
|
||||
|
||||
// SubscriptionManager::renew returns array (success) or false (failed)
|
||||
if (!$result) {
|
||||
return new WP_Error('renew_failed', __('Failed to create renewal order', 'woonoow'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'order_id' => $result['order_id'],
|
||||
'status' => $result['status']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich subscription with product and user info
|
||||
*/
|
||||
private static function enrich_subscription($subscription)
|
||||
{
|
||||
$enriched = (array) $subscription;
|
||||
|
||||
// Add product info
|
||||
$product_id = $subscription->variation_id ?: $subscription->product_id;
|
||||
$product = wc_get_product($product_id);
|
||||
$enriched['product_name'] = $product ? $product->get_name() : __('Unknown Product', 'woonoow');
|
||||
$enriched['product_image'] = $product ? wp_get_attachment_url($product->get_image_id()) : '';
|
||||
|
||||
// Add user info
|
||||
$user = get_userdata($subscription->user_id);
|
||||
$enriched['user_email'] = $user ? $user->user_email : '';
|
||||
$enriched['user_name'] = $user ? $user->display_name : __('Unknown User', 'woonoow');
|
||||
|
||||
// Add computed fields
|
||||
$enriched['is_active'] = $subscription->status === 'active';
|
||||
$enriched['can_pause'] = $subscription->status === 'active';
|
||||
$enriched['can_resume'] = $subscription->status === 'on-hold';
|
||||
$enriched['can_cancel'] = in_array($subscription->status, ['active', 'on-hold', 'pending']);
|
||||
|
||||
// Format billing info
|
||||
$period_labels = [
|
||||
'day' => __('day', 'woonoow'),
|
||||
'week' => __('week', 'woonoow'),
|
||||
'month' => __('month', 'woonoow'),
|
||||
'year' => __('year', 'woonoow'),
|
||||
];
|
||||
$interval = $subscription->billing_interval > 1 ? $subscription->billing_interval . ' ' : '';
|
||||
$period = $period_labels[$subscription->billing_period] ?? $subscription->billing_period;
|
||||
if ($subscription->billing_interval > 1) {
|
||||
$period .= 's'; // Pluralize
|
||||
}
|
||||
$enriched['billing_schedule'] = sprintf(__('Every %s%s', 'woonoow'), $interval, $period);
|
||||
|
||||
return $enriched;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user