以编程方式将 Woocommerce 订阅开关添加到购物车

2024-01-20

我正在寻找一种以编程方式将两个 Woocommerce 订阅变体之间的切换添加到购物车的方法。 我们正在构建一个无头 WP 网站,因此我不想使用链接来完成此操作,如中所述这个问题 https://stackoverflow.com/questions/48327381/switch-subscription-add-directly-to-cart

我尝试了以下简单的方法,但它不允许这样做,因为用户已经订阅了:

WC()->cart->add_to_cart( 1907, 1, 1908 );

我想模仿当用户转到升级/降级页面,选择新变体并按下“切换订阅”时发生的情况。通常这意味着用户被发送到购物车中的开关进行结账,我只想将其添加到购物车,因为我们有自己的结账。


主要问题是,Woocommerce 认为,当通过订阅转换表单以外的任何其他方式将订阅产品添加到购物车时,该订阅产品是不可购买的。该表单发送几个 GET 参数,这些参数通过一些额外的验证发送 WC,然后如果其他一切都有效,则允许将商品添加到购物车。

我的解决方案是用我自己的一些功能来模拟这种验证,这些功能大部分是从订阅插件中窃取的。

/**
 * Must be set to true before programmatically adding subscription switch to cart.
 *
 * @var  bool
 */
$kbhl_adding_switch_to_cart = false;

/**
 * Array filled with data about current subscription.
 * Should only be retrieved by kbhl_get_current_subscription_data().
 *
 * @var  array
 */
$kbhl_global_current_subscription_data = array();


/**
 * Cache whether a given product is purchasable or not to save running lots of queries for the same product in the same request
 *
 *      $is_purchasable_cache taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
 *
 * @var  array
 */
$kbhl_purchasable_cache = array();

/**
 * $user_subscriptions_to_product taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
 *
 * @var  array
 */
$kbhl_user_subscriptions_to_product = array();


/**
 * If a product is being marked as not purchasable because it is limited and the customer has a subscription,
 * but the current request is to switch the subscription, then mark it as purchasable.
 *
 *        Function is_purchasable_switch() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
 *
 * @param   bool $is_purchasable  Current purchasable status.
 * @param   obj  $product         Product being checked.
 *
 * @return  bool                  New purchasable status.
 */
function kbhl_is_purchasable_switch( $is_purchasable, $product ) {
    global $kbhl_purchasable_cache;

    $kbhl_current_subscription_data = kbhl_get_current_subscription_data();

    // Only process this filter if running custom add switch function.
    if ( ! empty( $kbhl_current_subscription_data['id'] ) ) {
        return $is_purchasable;
    }

    $product_key = wcs_get_canonical_product_id( $product );

    // Set an empty cache if one isn't set yet.
    if ( ! isset( $kbhl_purchasable_cache[ $product_key ] ) ) {
        $kbhl_purchasable_cache[ $product_key ] = array();
    }

    // Exit early if we've already determined this product's purchasability via switching.
    if ( isset( $kbhl_purchasable_cache[ $product_key ]['switch'] ) ) {
        return $kbhl_purchasable_cache[ $product_key ]['switch'];
    }

    // If the product is already purchasble, we don't need to determine it's purchasibility via switching/auto-switching.
    if ( true === $is_purchasable || ! is_user_logged_in() || ! wcs_is_product_switchable_type( $product ) || ! WC_Subscriptions_Product::is_subscription( $product->get_id() ) ) {
        $kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
        return $kbhl_purchasable_cache[ $product_key ]['switch'];
    }

    $user_id            = get_current_user_id();
    $product_limitation = wcs_get_product_limitation( $product );

    if ( 'no' == $product_limitation || ! wcs_user_has_subscription( $user_id, $product->get_id(), wcs_get_product_limitation( $product ) ) ) {
        $kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
        return $kbhl_purchasable_cache[ $product_key ]['switch'];
    }

    // Adding to cart.
    if ( array_key_exists( $kbhl_current_subscription_data['id'], kbhl_get_user_subscriptions_to_product( $product, $user_id, $product_limitation ) ) ) {
        $is_purchasable = true;
    }

    $kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
    return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
add_filter( 'woocommerce_subscription_is_purchasable', 'kbhl_is_purchasable_switch', 13, 2 );



/**
 * Gets a list of the customer subscriptions to a product with a particular limited status.
 *
 *        Function get_user_subscriptions_to_product() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
 *
 * @param WC_Product|int $product      The product object or product ID.
 * @param int            $user_id      The user's ID.
 * @param string         $limit_status The limit status.
 *
 * @return WC_Subscription[] An array of a customer's subscriptions with a specific status and product.
 */
function kbhl_get_user_subscriptions_to_product( $product, $user_id, $limit_status ) {
    global $user_subscriptions_to_product;
    $product_id = is_object( $product ) ? $product->get_id() : $product;
    $cache_key  = "{$product_id}_{$user_id}_{$limit_status}";

    if ( ! isset( $user_subscriptions_to_product[ $cache_key ] ) ) {
        // Getting all the customers subscriptions and removing ones without the product is more performant than querying for subscriptions with the product.
        $subscriptions = wcs_get_subscriptions(
            array(
                'customer_id' => $user_id,
                'status'      => $limit_status,
            )
        );

        foreach ( $subscriptions as $subscription_id => $subscription ) {
            if ( ! $subscription->has_product( $product_id ) ) {
                unset( $subscriptions[ $subscription_id ] );
            }
        }

        $user_subscriptions_to_product[ $cache_key ] = $subscriptions;
    }

    return $user_subscriptions_to_product[ $cache_key ];
}





/**
 * When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
 *
 * @since 1.4
 */

/**
 * When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
 *
 *       Function set_switch_details_in_cart() taken from plugins\woocommerce-subscriptions\includes\class-wc-subscriptions-switcher.php
 *
 * @param   array $cart_item_data    Current cart item data.
 * @param   int   $product_id        ID of current product.
 * @param   int   $variation_id      ID of current product variation.
 *
 * @return  array                   Updated cart item data.
 */
function kbhl_set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
    try {

        $kbhl_current_subscription_data = kbhl_get_current_subscription_data();
        if ( empty( $kbhl_current_subscription_data['id'] ) || empty( $kbhl_current_subscription_data['item'] ) ) {
            return $cart_item_data;
        }

        $subscription = wcs_get_subscription( $kbhl_current_subscription_data['id'] );

        // Requesting a switch for someone elses subscription.
        if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) {
            wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' );
            WC()->cart->empty_cart( true );
            return array();
        }

        $item = wcs_get_order_item( absint( $kbhl_current_subscription_data['item'] ), $subscription );

        // Else it's a valid switch.
        $product         = wc_get_product( $item['product_id'] );
        $parent_products = WC_Subscriptions_Product::get_parent_ids( $product );
        $child_products  = array();

        if ( ! empty( $parent_products ) ) {
            foreach ( $parent_products as $parent_id ) {
                $child_products = array_unique( array_merge( $child_products, wc_get_product( $parent_id )->get_children() ) );
            }
        }

        if ( $product_id != $item['product_id'] && ! in_array( $item['product_id'], $child_products ) ) {
            return $cart_item_data;
        }

        $next_payment_timestamp = $subscription->get_time( 'next_payment' );

        // If there are no more payments due on the subscription, because we're in the last billing period, we need to use the subscription's expiration date, not next payment date.
        if ( false == $next_payment_timestamp ) {
            $next_payment_timestamp = $subscription->get_time( 'end' );
        }

        $cart_item_data['subscription_switch'] = array(
            'subscription_id'        => $subscription->get_id(),
            'item_id'                => absint( $kbhl_current_subscription_data['item'] ),
            'next_payment_timestamp' => $next_payment_timestamp,
            'upgraded_or_downgraded' => '',
        );

        return $cart_item_data;

    } catch ( Exception $e ) {

        wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
        WC()->cart->empty_cart( true );
        return array();
    }
}
add_filter( 'woocommerce_add_cart_item_data', 'kbhl_set_switch_details_in_cart', 11, 3 );





/**
 * Gets subscription data for current user.
 *
 * @return  array  Subscription data, also stored to global variable $kbhl_global_current_subscription_data
 */
function kbhl_get_current_subscription_data() {
    global $kbhl_adding_switch_to_cart, $kbhl_global_current_subscription_data;

    if ( ! $kbhl_adding_switch_to_cart ) {
        return array();
    }

    if ( ! empty( $kbhl_global_current_subscription_data ) ) {
        return $kbhl_global_current_subscription_data;
    }

    $subscription_data = array();
    $subs              = wcs_get_users_subscriptions();

    if ( ! empty( $subs ) ) {
        foreach ( $subs as $sub ) {
            $subscription_data['id'] = $sub->get_id();

            foreach ( $sub->get_items() as $item_id => $item ) {
                $subscription_data['item'] = $item_id;
                break; // There should only be 1 order item.
            }

            break; // There should only be 1 subscription.
        }
    }

    $kbhl_global_current_subscription_data = $subscription_data;

    return $kbhl_global_current_subscription_data;
}

添加后,您可以将交换机添加到购物车,如下所示:

global $kbhl_adding_switch_to_cart; // If run inside a function.
WC()->cart->empty_cart( true );
$kbhl_adding_switch_to_cart = true;
WC()->cart->add_to_cart( 1907, 1, 1927 );
$kbhl_adding_switch_to_cart = false; // Reset after to get back to default validation.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以编程方式将 Woocommerce 订阅开关添加到购物车 的相关文章

  • 如何编写在正文中包含锚标记的 Zend Framework URL?

    使用 Zend Framework 中设置的标准 MVC 我希望能够显示始终具有锚点的页面 现在我只是在 phtml 文件中添加一个带有 anchor 的无意义参数
  • 如何在php中使用preg添加html属性

    我正在寻找在 php 中编写一个脚本来扫描 html 文档并根据它找到的内容向元素添加新标记 更具体地说 我是扫描文档并为每个元素搜索CSS标记 float right left 如果找到它 它会添加align right left 基于它
  • 如何在 codeigniter 查询中使用 FIND_IN_SET?

    array array classesID gt 6 this gt db gt select gt from this gt table name gt where array gt order by this gt order by q
  • 为什么 PHP 中不允许“传统”类型提示?

    刚刚发现类型提示 http php net manual en language oop5 typehinting phpPHP 中允许 但不适用于整数 字符串 布尔值或浮点数 为什么 PHP 不允许对整数 字符串等类型进行类型提示 从 P
  • 如何在 Zend Framework 中处理移动设备?

    我接手了一个噩梦般的项目 我正在迁移一个写得很差的站点 并慢慢地将其迁移到 Zend Framework 应用程序中 不幸的是 我没有时间做补救工作 使这变得可以忍受 也许是一个或两个模型 我现在被告知该网站很快就会有移动版本 建议是克隆旧
  • 如何确定当前使用哪个网格选项

    我将 Bootstrap 3 用于使用 PHP 和 HTML 创建的网页 随着响应式网格和类的开启引导程序3您可以将多个类分配给一个 div 以根据当前屏幕尺寸定义不同的宽度 例如 div class col lg 3 col md 3 c
  • 如何将粘在一起的单词分开?

    我有很多命名不好的文件 videoofmegoingtoschool avi 是否有一个库或某种算法可以正确地将其分离 video of me going to school avi 我不认为那里有什么 我可以想象一个程序 它使用单词词典并
  • jquery上传完成后重定向到新页面

    我正在尝试让这个 jquery 工具与我的网站一起使用以进行文件上传 https github com blueimp jQuery File Upload https github com blueimp jQuery File Uplo
  • 在一个后台为MYSQL的网站上集成搜索

    我有一个位置搜索website http www jammulinks com对于一个城市 我们首先收集该城市所有可能类别的数据 如学校 学院 百货商店等 并将其信息存储在单独的表中 因为每个条目除了名称 地址和电话号码外都有不同的详细信息
  • Laravel 5.4 升级 - 违反完整性约束 - 列不能为空

    奇怪的是 所有这些都在 5 2 中工作 但我不知道可以改变什么来实现这一点 下面是错误和正在插入的数组 SQLSTATE 23000 Integrity constraint violation 1048 Column gender can
  • PHP、jQuery 和 Ajax 调用乱序

    我正在使用 jQuery 进行 Ajax 调用 我有 x 数量的 Ajax 调用附加到 div 这些 Ajax 加载请求是由 PHP foreach 循环生成的 问题是它们渲染的顺序不正确 它们被设置在数组中
  • 创建 Facebook 测试用户时访问令牌出现问题

    我正在尝试为我的 Facebook 应用程序创建测试用户 他们在 11 月份的博客文章 http developers facebook com blog post 429 中宣布了此功能 并在此处记录了该功能 http developer
  • 删除 woocommerce 店面主页标题 php

    我正在使用 woocommerce 的店面主题 我需要用 php 删除主页标题 h1 我知道 css 解决方案 但我不想使用它 因为我想将 h1 添加到该页面的其他位置 并且在一个页面中包含 2 个 h1 对 seo 不利页 我也知道删除页
  • 如何在 Twitter Card 中传递动态图像路径?

    我在用
  • MySQL 数据库无法在 XAMPP for Mac 上启动

    突然我在 mac 上遇到了这个问题 我无法启动我的 MySQL 数据库 我只能启动 ProFTPD 和 Apache Web Server 这是应用程序日志 Starting all servers Starting MySQL Datab
  • 我怎样才能让这个脚本在 WordPress 上运行?

    我有这个脚本 document ready function text1 click function this hide 代码html div class div1 p class text1 text to appear when th
  • magento成功页面变量

    我正在尝试捕获一些 magento 成功页面变量以传递给我们的广告公司 到目前为止 我已经得到了这个 但变量没有输出任何内容 数据需要采用以下格式 price1 price2 price3 qty1 qty2 qty3 sku1 sku2
  • wordpress - 像 stackoverflow 中那样内嵌 ajax 注释

    我有一个 WordPress 博客 希望为人们提供与 stackoverflow 中添加评论相同的用户体验 有很多评论 ajax 插件 但我找不到一个可以使用的插件 它允许您在主页上内联 进入并添加评论 而无需先深入到单独的单个帖子页面 任
  • HTML 代码中的 PHP [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我用 HTML 代码编写了 PHP div div 但这出现在输出页面中 else print 我怎样才能让PHP执行 你的文件有一个 p
  • 合并 url 中的 2 个输入值

    我有这样的形式

随机推荐

  • 将 Crystal Reports 连接到 SQL Server

    所以我进入了 数据库专家 但我似乎不知道如何将数据库添加到报告中 有任何想法吗 附 我正在使用 CR 13 和 SQL Server 2012 我们正在使用 CR 11 R2 我不确定 CR 2013 的选项是否已更改 但在 CR 11 R
  • JavaScript 对象使用 .检索值[重复]

    这个问题在这里已经有答案了 可能的重复 如何使用动态键访问对象 https stackoverflow com questions 6921803 how to access object using dynamic key 我有多个具有不
  • Spring:类文件版本错误 61.0,应该是 55.0

    我正在使用 Java 11 的 Maven 和 IntelliJ IDEA 上的 Maven 我正在尝试创建一个 JsonTset 类 如下所示 import org junit BeforeClass import org junit T
  • Jersey - 有没有办法用参数实例化每个请求资源?

    假设我有这样的课程 Path test public class TestResource private TestService testService public TestResource TestService testServic
  • 如何解决 Argo 输出参数大小限制?

    我有一个在 JSON 数组上循环的 Argo 工作流程 当列表变得太大时 我会收到如下错误 time some time level fatal msg Pod some pod name is invalid metadata annot
  • MongoDB 副本集状态未从启动更改为辅助

    我已经设置了一个包含 3 个节点的 MongoDB 副本集 虚拟机运行 CentOS 一个节点成为主节点 另外 2 个节点陷入启动状态 当这两个节点将其状态从启动更改为辅助时 aryabhata PRIMARY gt rs status s
  • Python:使用 lineno 引发语法错误

    我正在为特定于域的语言实现一个解析器 并且希望能够引发语法错误 引发此异常时如何设置文件名 行号和偏移量 例外 语法错误 当解析器遇到语法时引发 错误 这可能发生在 import 语句 exec 语句 调用内置函数 eval 或 input
  • 树枝变量中的树枝变量

    我有一个树枝变量html 为了在树枝模板中显示它 我这样做 html 该变量看起来像 div region top div div region center div region 也是一个变量 当 Twig 解析我的html变量 它不解析
  • 检测 jQuery 中 input[type=text] 的值变化

    我想在每次特定输入框的值发生变化时执行一个函数 它almost与 input keyup function 但是例如 将文本粘贴到框中时什么也不会发生 input change function 仅在输入模糊时触发 那么我如何立即知道文本框
  • GCC 相当于 MS 的 /bigobj

    我们正在大量使用boost serialization和一般模板 一切似乎都很顺利 不过 我们在 Windows 构建上遇到了障碍 这似乎会导致目标文件太大的问题 我们使用 MinGW Msys 和 g 4 7 0 c mingw bin
  • Flutter 删除所有路由

    我想开发一个注销按钮 它将把我发送到登录路线并从登录路线中删除所有其他路线Navigator 该文档似乎没有解释如何制作RoutePredicate或具有任何类型的removeAll 功能 我能够使用以下代码来完成此操作 Navigator
  • 我可以将参数传递给 rake db:seed 吗?

    我的一部分seeds rb将大量数据加载到数据库中 我希望能够有选择地加载这些数据 例如 rake db seed or rake db seed 0 只会加载运行网站所需的数据 而 rake db seed 1 也会将我的大数据文件加载到
  • Ember 数据无法读取未定义的属性“async”

    将 Ember v1 8 beta 3 与 Ember Data 1 0 beta 10 一起使用 您会收到以下错误 Error while processing route index Cannot read property async
  • 生成指定范围内的随机数 - 各种情况(int、float、inclusive、exclusive)

    given a Math random 返回 0 1 之间的数字的函数min max值来指定范围 我们如何为以下情况生成数字 我们想要的案例integer A min max B min max return Math floor Math
  • 如何更改jquery对话框按钮

    我想用我自己的按钮图像替换 jquery 对话框按钮 这样做最简洁的方法是什么 按钮上不会覆盖任何文本 我正在使用 jquery 1 4 2 和 jquery ui 1 8 1 不要应用 jQuery UI 使用的 CSS 选择器 使用具有
  • Global.asax 事件:Application_OnPostAuthenticateRequest

    我在用Application OnPostAuthenticateRequest事件在global asax to get a 经过身份验证的用户的角色和权限我还制作了自定义主体类来获取用户详细信息以及角色和权限 b 获取对该用户而言保持不
  • Jquery 动画无法正确处理列表项

    我有一个垂直的项目列表 每个项目都有一个删除按钮 当我点击其中一个的删除时 我希望下面的那些能够平滑地向上滑动 此时它们正在跳跃 下面是代码 http codepen io ovesyan19 pen chDgy http codepen
  • 这个&符号是什么意思? [复制]

    这个问题在这里已经有答案了 可能的重复 只需观看一些 Railscast 即可看到如下代码 Category Product Person each delete all 我知道它会删除这些模型的所有记录 但我不知道这是什么 delete
  • 在 iPad 上的 SwiftUI 中呈现 ActionSheet

    我已经得到了一个可以在 iPhone 设备上很好地呈现的 ActionSheet 但它在 iPad 上会崩溃 说它需要弹出窗口的位置 有人对这段代码感到幸运吗 我正在使用 iOS 13 beta 3 和 Xcode 11 beta 3 这使
  • 以编程方式将 Woocommerce 订阅开关添加到购物车

    我正在寻找一种以编程方式将两个 Woocommerce 订阅变体之间的切换添加到购物车的方法 我们正在构建一个无头 WP 网站 因此我不想使用链接来完成此操作 如中所述这个问题 https stackoverflow com questio