Misc fixes: cleanup templates and supporting updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# WooNooW Feature Roadmap - 2025
|
||||
|
||||
**Last Updated**: December 31, 2025
|
||||
**Last Updated**: June 1, 2026
|
||||
**Status**: Active Development
|
||||
|
||||
This document outlines the comprehensive feature roadmap for WooNooW, building upon existing infrastructure.
|
||||
@@ -301,66 +301,59 @@ class AffiliateTracker {
|
||||
### Overview
|
||||
Recurring product subscriptions with flexible billing cycles.
|
||||
|
||||
### Status: **Planning** 🔵
|
||||
### Status: **Shipped** ✅
|
||||
|
||||
### What's Already Built
|
||||
- ✅ Product management
|
||||
- ✅ Order system
|
||||
- ✅ Payment gateways
|
||||
- ✅ Notification system
|
||||
- ✅ Database tables (`wp_woonoow_subscriptions`, `wp_woonoow_subscription_orders`) — schema below reflects actual shipped columns
|
||||
- ✅ Per-gateway auto-renew capability table (kill-switchable)
|
||||
- ✅ Pause/resume/cancel/early-renew customer UI
|
||||
- ✅ Admin list with bulk actions, search, and per-status filter
|
||||
- ✅ Renewal cron (`process_renewals`, `check_expirations`, `send_reminders`, `retry_unpaid_renewals`)
|
||||
|
||||
### What's Needed
|
||||
### Schema (as shipped)
|
||||
|
||||
#### 1. Database Tables
|
||||
```sql
|
||||
wp_woonoow_subscriptions (id, customer_id, product_id, status, billing_period, billing_interval, price, next_payment_date, start_date, end_date, trial_end_date)
|
||||
wp_woonoow_subscription_orders (id, subscription_id, order_id, payment_status, created_at)
|
||||
wp_woonoow_subscriptions (
|
||||
id, user_id, order_id, product_id, variation_id, status,
|
||||
billing_period, billing_interval, recurring_amount,
|
||||
start_date, trial_end_date, next_payment_date, end_date, last_payment_date,
|
||||
payment_method, payment_meta, cancel_reason,
|
||||
pause_count, failed_payment_count, reminder_sent_at
|
||||
)
|
||||
wp_woonoow_subscription_orders (
|
||||
id, subscription_id, order_id, order_type ENUM 'parent'|'renewal'|'switch'|'resubscribe'
|
||||
)
|
||||
```
|
||||
|
||||
#### 2. Product Meta
|
||||
Add subscription options to product:
|
||||
- Is subscription product (checkbox)
|
||||
- Billing period (daily, weekly, monthly, yearly)
|
||||
- Billing interval (e.g., 2 for every 2 months)
|
||||
- Trial period (days)
|
||||
Note: the column is `user_id`, not `customer_id` — the original spec used the
|
||||
WC-style "customer" naming, but WP schema reserves `customer` for the legacy
|
||||
WP customer user role and the column was renamed before the first migration
|
||||
shipped.
|
||||
|
||||
#### 3. Renewal System
|
||||
```php
|
||||
class SubscriptionRenewal {
|
||||
|
||||
// WP-Cron daily job
|
||||
public function process_renewals() {
|
||||
$due_subscriptions = $this->get_due_subscriptions();
|
||||
|
||||
foreach ($due_subscriptions as $subscription) {
|
||||
// Create renewal order
|
||||
// Process payment
|
||||
// Update next payment date
|
||||
// Send notification
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Customer Dashboard
|
||||
### Customer Dashboard
|
||||
**Route**: `/account/subscriptions`
|
||||
- Active subscriptions list
|
||||
- Pause/resume subscription
|
||||
- Pause/resume subscription (capped at `max_pause_count` setting, default 3)
|
||||
- Cancel subscription
|
||||
- Update payment method
|
||||
- View billing history
|
||||
- Change billing cycle
|
||||
|
||||
#### 5. Admin UI
|
||||
**Route**: `/products/subscriptions`
|
||||
- All subscriptions list
|
||||
- Filter by status
|
||||
- View subscription details
|
||||
- Manual renewal
|
||||
### Admin UI
|
||||
**Route**: `/subscriptions`
|
||||
- All subscriptions list with checkbox + bulk actions (cancel, CSV export)
|
||||
- Free-text search by id / email / display name
|
||||
- Per-status filter
|
||||
- View subscription details (per-gateway auto-renew badge, pause count)
|
||||
- Renew Now (creates manual order) or Charge Now (forces auto-debit, M2)
|
||||
- Cancel/refund
|
||||
|
||||
### Priority: **Low** 🟢
|
||||
### Effort: 4-5 weeks
|
||||
### Priority: ~~Low~~ Shipped ✅
|
||||
### Effort: ~~4-5 weeks~~ Shipped
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { api } from '@/lib/api';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { __ } from '@/lib/i18n';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"types": [],
|
||||
"baseUrl": ".",
|
||||
"paths": { "@/*": ["./src/*"] },
|
||||
"ignoreDeprecations": "6.0"
|
||||
"ignoreDeprecations": "5.0"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -352,6 +352,21 @@ class EmailRenderer
|
||||
'payment_link' => $data['payment_link'] ?? '',
|
||||
];
|
||||
|
||||
// O1 — Derive `billing_schedule` (e.g. "Every 3 Months") and
|
||||
// `payment_method_title` (e.g. "Stripe" rather than the raw
|
||||
// gateway id "stripe"). The data is on the subscription row but
|
||||
// isn't pre-formatted. We rebuild both so email templates can
|
||||
// show the merchant-friendly string without duplicating the
|
||||
// pluralization + lookup logic.
|
||||
$sub_variables['billing_schedule'] = self::format_billing_schedule(
|
||||
isset($sub->billing_period) ? (string) $sub->billing_period : '',
|
||||
isset($sub->billing_interval) ? (int) $sub->billing_interval : 1
|
||||
);
|
||||
$sub_variables['payment_method_title'] = self::resolve_payment_method_title(
|
||||
isset($sub->payment_method) ? (string) $sub->payment_method : '',
|
||||
$data['order'] ?? null
|
||||
);
|
||||
|
||||
// Get product name if not already set
|
||||
if (!isset($variables['product_name']) && isset($data['product']) && $data['product'] instanceof \WC_Product) {
|
||||
$sub_variables['product_name'] = $data['product']->get_name();
|
||||
@@ -381,6 +396,57 @@ class EmailRenderer
|
||||
return apply_filters('woonoow_email_variables', $variables, $event_id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* O1 — Format a billing schedule string like "Every 3 Months" from raw
|
||||
* period and interval columns. Mirrors the controller's
|
||||
* `enrich_subscription()` math so email templates show the same string
|
||||
* the customer sees in the SPA. Falls back to the period string itself
|
||||
* if the period is unknown.
|
||||
*/
|
||||
public static function format_billing_schedule($period, $interval)
|
||||
{
|
||||
$period_labels = [
|
||||
'day' => __('day', 'woonoow'),
|
||||
'week' => __('week', 'woonoow'),
|
||||
'month' => __('month', 'woonoow'),
|
||||
'year' => __('year', 'woonoow'),
|
||||
];
|
||||
$interval = max(1, (int) $interval);
|
||||
$period_label = $period_labels[$period] ?? $period;
|
||||
if ($interval > 1) {
|
||||
$period_label .= 's';
|
||||
}
|
||||
return sprintf(__('Every %s%s', 'woonoow'), $interval, $period_label);
|
||||
}
|
||||
|
||||
/**
|
||||
* O1 — Resolve a human-friendly payment method title from a stored gateway
|
||||
* id. Order of preference:
|
||||
* 1. The order's `payment_method_title` (most accurate; set by gateway
|
||||
* at checkout — e.g. "PayPal — Visa ending in 1234")
|
||||
* 2. The registered WC gateway's `get_title()` (e.g. "Stripe")
|
||||
* 3. The raw id
|
||||
*/
|
||||
public static function resolve_payment_method_title($gateway_id, $order = null)
|
||||
{
|
||||
if ($order instanceof \WC_Order) {
|
||||
$title = $order->get_payment_method_title();
|
||||
if (!empty($title)) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
if ($gateway_id !== '' && function_exists('WC') && WC()->payment_gateways()) {
|
||||
$gateways = WC()->payment_gateways()->payment_gateways();
|
||||
if (isset($gateways[$gateway_id]) && method_exists($gateways[$gateway_id], 'get_title')) {
|
||||
$title = $gateways[$gateway_id]->get_title();
|
||||
if (!empty($title)) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $gateway_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse [card] tags and convert to HTML
|
||||
*
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Notification Template Provider
|
||||
*
|
||||
* Manages notification templates for all channels.
|
||||
*
|
||||
* @package WooNooW\Core\Notifications
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core\Notifications;
|
||||
|
||||
use WooNooW\Email\DefaultTemplates as EmailDefaultTemplates;
|
||||
|
||||
class TemplateProvider {
|
||||
|
||||
/**
|
||||
* Option key for storing templates
|
||||
*/
|
||||
const OPTION_KEY = 'woonoow_notification_templates';
|
||||
|
||||
/**
|
||||
* Get all templates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_templates() {
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
// Merge with defaults
|
||||
$defaults = self::get_default_templates();
|
||||
|
||||
return array_merge($defaults, $templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template for specific event and channel
|
||||
*
|
||||
* @param string $event_id Event ID
|
||||
* @param string $channel_id Channel ID
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get_template($event_id, $channel_id, $recipient_type = 'customer') {
|
||||
$templates = self::get_templates();
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
if (isset($templates[$key])) {
|
||||
return $templates[$key];
|
||||
}
|
||||
|
||||
// Return default if exists
|
||||
$defaults = self::get_default_templates();
|
||||
|
||||
if (isset($defaults[$key])) {
|
||||
return $defaults[$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save template
|
||||
*
|
||||
* @param string $event_id Event ID
|
||||
* @param string $channel_id Channel ID
|
||||
* @param array $template Template data
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return bool
|
||||
*/
|
||||
public static function save_template($event_id, $channel_id, $template, $recipient_type = 'customer') {
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
$templates[$key] = [
|
||||
'event_id' => $event_id,
|
||||
'channel_id' => $channel_id,
|
||||
'recipient_type' => $recipient_type,
|
||||
'subject' => $template['subject'] ?? '',
|
||||
'body' => $template['body'] ?? '',
|
||||
'variables' => $template['variables'] ?? [],
|
||||
'updated_at' => current_time('mysql'),
|
||||
];
|
||||
|
||||
return update_option(self::OPTION_KEY, $templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete template (revert to default)
|
||||
*
|
||||
* @param string $event_id Event ID
|
||||
* @param string $channel_id Channel ID
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete_template($event_id, $channel_id, $recipient_type = 'customer') {
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
if (isset($templates[$key])) {
|
||||
unset($templates[$key]);
|
||||
return update_option(self::OPTION_KEY, $templates);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WooCommerce email template content
|
||||
*
|
||||
* @param string $email_id WooCommerce email ID
|
||||
* @return array|null
|
||||
*/
|
||||
private static function get_wc_email_template($email_id) {
|
||||
if (!function_exists('WC')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mailer = \WC()->mailer();
|
||||
$emails = $mailer->get_emails();
|
||||
|
||||
if (isset($emails[$email_id])) {
|
||||
$email = $emails[$email_id];
|
||||
return [
|
||||
'subject' => $email->get_subject(),
|
||||
'heading' => $email->get_heading(),
|
||||
'enabled' => $email->is_enabled(),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default templates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_default_templates() {
|
||||
$templates = [];
|
||||
|
||||
// Get all events from EventRegistry (single source of truth)
|
||||
$all_events = EventRegistry::get_all_events();
|
||||
|
||||
// Get email templates from DefaultTemplates
|
||||
$allEmailTemplates = EmailDefaultTemplates::get_all_templates();
|
||||
|
||||
foreach ($all_events as $event) {
|
||||
$event_id = $event['id'];
|
||||
$recipient_type = $event['recipient_type'];
|
||||
// Get template body from the new clean markdown source
|
||||
$body = $allEmailTemplates[$recipient_type][$event_id] ?? '';
|
||||
$subject = EmailDefaultTemplates::get_default_subject($recipient_type, $event_id);
|
||||
|
||||
// If template doesn't exist, create a simple fallback
|
||||
if (empty($body)) {
|
||||
$body = "[card]\n\n## Notification\n\nYou have a new notification about {$event_id}.\n\n[/card]";
|
||||
$subject = __('Notification from {store_name}', 'woonoow');
|
||||
}
|
||||
|
||||
$templates["{$recipient_type}_{$event_id}_email"] = [
|
||||
'event_id' => $event_id,
|
||||
'channel_id' => 'email',
|
||||
'recipient_type' => $recipient_type,
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
'variables' => self::get_variables_for_event($event_id),
|
||||
];
|
||||
}
|
||||
|
||||
// Add push notification templates
|
||||
$templates['staff_order_placed_push'] = [
|
||||
'event_id' => 'order_placed',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'staff',
|
||||
'subject' => __('New Order #{order_number}', 'woonoow'),
|
||||
'body' => __('New order from {customer_name} - {order_total}', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
$templates['customer_order_processing_push'] = [
|
||||
'event_id' => 'order_processing',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'customer',
|
||||
'subject' => __('Order Processing', 'woonoow'),
|
||||
'body' => __('Your order #{order_number} is being processed', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
$templates['customer_order_completed_push'] = [
|
||||
'event_id' => 'order_completed',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'customer',
|
||||
'subject' => __('Order Completed', 'woonoow'),
|
||||
'body' => __('Your order #{order_number} has been completed!', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
$templates['staff_order_cancelled_push'] = [
|
||||
'event_id' => 'order_cancelled',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'staff',
|
||||
'subject' => __('Order Cancelled', 'woonoow'),
|
||||
'body' => __('Order #{order_number} has been cancelled', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
$templates['customer_order_refunded_push'] = [
|
||||
'event_id' => 'order_refunded',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'customer',
|
||||
'subject' => __('Order Refunded', 'woonoow'),
|
||||
'body' => __('Your order #{order_number} has been refunded', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
$templates['staff_low_stock_push'] = [
|
||||
'event_id' => 'low_stock',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'staff',
|
||||
'subject' => __('Low Stock Alert', 'woonoow'),
|
||||
'body' => __('{product_name} is running low on stock', 'woonoow'),
|
||||
'variables' => self::get_product_variables(),
|
||||
];
|
||||
$templates['staff_out_of_stock_push'] = [
|
||||
'event_id' => 'out_of_stock',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'staff',
|
||||
'subject' => __('Out of Stock Alert', 'woonoow'),
|
||||
'body' => __('{product_name} is now out of stock', 'woonoow'),
|
||||
'variables' => self::get_product_variables(),
|
||||
];
|
||||
$templates['customer_new_customer_push'] = [
|
||||
'event_id' => 'new_customer',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'customer',
|
||||
'subject' => __('Welcome!', 'woonoow'),
|
||||
'body' => __('Welcome to {store_name}, {customer_name}!', 'woonoow'),
|
||||
'variables' => self::get_customer_variables(),
|
||||
];
|
||||
$templates['customer_customer_note_push'] = [
|
||||
'event_id' => 'customer_note',
|
||||
'channel_id' => 'push',
|
||||
'recipient_type' => 'customer',
|
||||
'subject' => __('Order Note Added', 'woonoow'),
|
||||
'body' => __('A note has been added to order #{order_number}', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variables for a specific event
|
||||
*
|
||||
* @param string $event_id Event ID
|
||||
* @return array
|
||||
*/
|
||||
private static function get_variables_for_event($event_id) {
|
||||
// Product events
|
||||
if (in_array($event_id, ['low_stock', 'out_of_stock'])) {
|
||||
return self::get_product_variables();
|
||||
}
|
||||
|
||||
// Customer events (but not order-related)
|
||||
if ($event_id === 'new_customer') {
|
||||
return self::get_customer_variables();
|
||||
}
|
||||
|
||||
// Subscription events
|
||||
if (strpos($event_id, 'subscription_') === 0) {
|
||||
return self::get_subscription_variables();
|
||||
}
|
||||
|
||||
// All other events are order-related
|
||||
return self::get_order_variables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available order variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_variables() {
|
||||
return [
|
||||
'order_number' => __('Order Number', 'woonoow'),
|
||||
'order_total' => __('Order Total', 'woonoow'),
|
||||
'order_status' => __('Order Status', 'woonoow'),
|
||||
'order_date' => __('Order Date', 'woonoow'),
|
||||
'order_url' => __('Order URL', 'woonoow'),
|
||||
'order_items_list' => __('Order Items (formatted list)', 'woonoow'),
|
||||
'order_items_table' => __('Order Items (formatted table)', 'woonoow'),
|
||||
'payment_method' => __('Payment Method', 'woonoow'),
|
||||
'payment_url' => __('Payment URL (for pending payments)', 'woonoow'),
|
||||
'shipping_method' => __('Shipping Method', 'woonoow'),
|
||||
'tracking_number' => __('Tracking Number', 'woonoow'),
|
||||
'refund_amount' => __('Refund Amount', 'woonoow'),
|
||||
'customer_name' => __('Customer Name', 'woonoow'),
|
||||
'customer_email' => __('Customer Email', 'woonoow'),
|
||||
'customer_phone' => __('Customer Phone', 'woonoow'),
|
||||
'billing_address' => __('Billing Address', 'woonoow'),
|
||||
'shipping_address' => __('Shipping Address', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'store_email' => __('Store Email', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available product variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_product_variables() {
|
||||
return [
|
||||
'product_name' => __('Product Name', 'woonoow'),
|
||||
'product_sku' => __('Product SKU', 'woonoow'),
|
||||
'product_url' => __('Product URL', 'woonoow'),
|
||||
'stock_quantity' => __('Stock Quantity', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available customer variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_customer_variables() {
|
||||
return [
|
||||
'customer_name' => __('Customer Name', 'woonoow'),
|
||||
'customer_email' => __('Customer Email', 'woonoow'),
|
||||
'customer_phone' => __('Customer Phone', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'store_email' => __('Store Email', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available subscription variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscription_variables() {
|
||||
return [
|
||||
'subscription_id' => __('Subscription ID', 'woonoow'),
|
||||
'subscription_status' => __('Subscription Status', 'woonoow'),
|
||||
'product_name' => __('Product Name', 'woonoow'),
|
||||
'billing_period' => __('Billing Period (e.g., Monthly)', 'woonoow'),
|
||||
'recurring_amount' => __('Recurring Amount', 'woonoow'),
|
||||
'next_payment_date' => __('Next Payment Date', 'woonoow'),
|
||||
'end_date' => __('Subscription End Date', 'woonoow'),
|
||||
'cancel_reason' => __('Cancellation Reason', 'woonoow'),
|
||||
'customer_name' => __('Customer Name', 'woonoow'),
|
||||
'customer_email' => __('Customer Email', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'my_account_url' => __('My Account URL', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace variables in template
|
||||
*
|
||||
* @param string $content Content with variables
|
||||
* @param array $data Data to replace variables
|
||||
* @return string
|
||||
*/
|
||||
public static function replace_variables($content, $data) {
|
||||
foreach ($data as $key => $value) {
|
||||
$content = str_replace('{' . $key . '}', $value, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -338,7 +338,7 @@ class TemplateOverride
|
||||
if (is_wc_endpoint_url('order-pay')) {
|
||||
global $wp;
|
||||
$order_id = $wp->query_vars['order-pay'];
|
||||
wp_redirect($build_route('order-pay/' . $order_id), 302);
|
||||
wp_redirect($build_route('checkout/pay/' . $order_id), 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user