Woocommerce主题开发

卸载

是否保留数据

1
2
//告诉WCC,删除插件的时候,是否保留数据
define('WC_REMOVE_ALL_DATA', true);

角色

安装woocommerce之后会自动创建两个角色

1、店铺管理员(Shop manager)

2、顾客(Customer)

固定链接

后台->设置->固定链接

产品固定链接

  • 默认
1
http://wordpress.localhost/product/sample-product/
  • 商店基础链接
1
http://wordpress.localhost/shop/sample-product/
  • 添加网店和分类目录地址
1
http://wordpress.localhost/shop/product-category/sample-product/
  • 自定义基础链接
1
2
3
//输入个自定义base,它必须被设置,否则 WordPress 会使用默认值替代.

/product/

产品常规设置

评论设置

  • 启用产品评价

  • 用户评论时显示”已验证的所有者“标签(用来标识用户是否为已购买的用户)

  • 仅”已验证的用户“可参与评价(已购买的用户才可以评价)

库存设置

  • 保留库存(保留库存(为未支付订单)x 分钟。当达到限制时间时,待付款的订单将会被取消。留空则一直保留,不会自动取消订单。)
  • 库存不足的最低值(当库存数量达到这个数量时,会发送通知)
  • 售罄的最低值(当库存达到这里数量时,库存状态将会改编为”售罄“,并发送通知)

可下载设置

  • 文件下载方法
    • 强制下载
    • X-Accel-Redirect/X-Sendfile(需要服务器配置)
    • 仅重定向(在发布产品时,可以设置对应文件的url)
  • 访问限制
    • 下载必须登录
    • 支付后授权访问可下载产品(启用此选项时当下载订单状态为“正在处理”,而非“已完成”,授权访问下载资源。否则,订单状态为”已完成“才能下载)

配送设置

配送方式

免费配送

  • N/A (无限制)
  • 免费送货需要免费送货券
  • 最小订单金额
  • 最小订单金额或优惠券
  • 最小订单金额和优惠券

标准运费(统一费率)

  • 税状态

    • 应纳税
  • 成本

    • 设置固定值
    • 设置表达式
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    //[qty]表示商品数量
    //[cost]表示商品总额
    //[fee percent="10" min_fee="20" max_fee=""]
    //运费每件商品10元
    10*[qty]
    //运费为商品总额的10%
    [cost]*0.1
    [fee percent="10"]
    //运费为商品总额的10%,最低收5元,最高30元
    [fee percent="10" min_fee="5" max_fee="30"]
    

本地自提

  • 税状态
    • 应纳税
  • 成本(只能设置固定值)

通过代码设置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//如果配送符合免费,则仅仅展示免费
add_filter('woocommerce_package_rates', function($rates) {
    $free = [];
    foreach($rates as $rate_id => $rate) {
        if ('free_shipping' === $rate->method_id) {
            $free[$rate_id] = $rate;
            break;
        }
    }
    return !empty($free) ? $free : $rates;
}, 100);

配送类型

”配送类型“针对单个产品设置的,可以看作是”配送方式->统一费率(标准运费)“中更细粒度的设置

  • 产品编辑页面可以单独设置运费类别

  • 如果设置了”配送类型“,在统一费率的编辑界面可以设置”配送类型“的费用。

    • 配送类型的费用也可以采用前面成本中的公式[qty]、[cost]等
    • 如果设置了成本和配送类型的费用,实际的运费是两者的计算结果之和
  • 计算方式

    • 每个配送类型:每个配送类型单独收费
    • 每个订单:按最贵的配送类型进行收费

结算设置

优惠券使用

  • 启用优惠代码的使用

是否启用优惠券功能

  • 顺序计算优惠券折扣

当应用多个优惠券,应用第一个优惠券的全价和第二优待券的折扣价格等。

即:按顺序,先根据前面的优惠券计算出应付价格,再结合后面的优惠券计算折扣。

API设置

Rest API

查看所有的路由:http://wordpress.localhost/wp-json/wc/v2

  • 生成Api Key
  • OAuth 1.0 (http)
  • Basic Auth (https)

Webhook

特定的时候要求wcc向指定的页面发送post请求。webbook是一种通知机制。

优惠券

常规

  • 折扣类型

    • 百分比优惠

    优惠券的金额就是比例,如:10 标识 10%的优惠

    • 固定的购物车优惠

    整个购物车固定金额的优惠。如果购物车中的商品总金额小于优惠券金额,则优惠总额为商品总金额

    • 固定的产品优惠

    按产品数据计算优惠。如产品数量为2,优惠券金额为8,则优惠金额为2*8=16

  • 优惠券金额

  • 允许免运费

要配合配送方式中的免费配送的设置使用。其中免费送货需要免费送货券、最小订单金额或优惠券、最小订单金额和优惠券这三个设置都可以

  • 优惠券过期时间

精确到天,过期时间为设置日期的00:00:00

使用限制

  • 最低花费金额
  • 最高花费金额
  • 仅限单独使用

如果优惠券不能与其他优惠券一起使用请勾选此框

  • 不含促销产品

如果优惠券不适用用于促销产品,请选中该复选框。基于物品的优惠券仅在该物品非促销时可用。基于购物车的优惠券仅在购物车内无促销产品时可用

  • 产品
  • 排除产品

折扣类型是固定的购物车优惠时候,如果购物车中包含了排除的产品,整个购物车都无法使用该优惠券

  • 产品类别
  • 排除类别
  • 获准的电子邮件

使用次数

  • 每个优惠券的使用次数限制
  • 限制x个物品使用 (只用于折扣类型:百分比优惠、固定的产品优惠)

如果购物车中有多个商品,优先级为 数量 > 金额

  • 每个用户的使用次数限制

订单

状态 (7种)

  • 待付款 (Pending payment)
  • 正在处理(Processing)
  • 保留(On hold)
  • 已完成(Completed)
  • 已取消(Cancelled)
  • 已退款(Refunded)
  • 失败订单(Failed)

动作

  • 发给客户的邮件收据/订单详情
  • 重新发送新订单通知 (发送给管理员)
  • 重新生成下载权限 (下载剩余次数会被重置)

产品

发布

发布时间

可用于实现定时发布的功能,仅当当前时间大于发布时间,产品才显示

目录可见

  • 商店页和搜索结果页
  • 仅商店页
  • 仅搜索结果页
  • 隐藏

产品数据

产品类型

  • 单个产品
    • 实物产品
      • 可下载
    • 虚拟产品
      • 可下载
  • 成组产品 (包含多个产品,类似套餐)
  • 外接/关联产品
  • 可变产品 (产品的多个属性可选,可以选择不同组合。如:产品尺寸,产品颜色,不同颜色和不同尺寸组成不同的组合。)
    • 属性 (可以勾上是否用于变量)
    • 变量
      • 自动生成 (自动根据勾选用于变量的属性,通过组合方式生成不同的变量,即:可能产品)
      • 手动添加

库存

  • SKU

SKU,即库存单位,是一种可以帮助零售商识别最小库存持有量的代码。每个 SKU 都是独一无二的,代表了一个特定的商品,包括它的型号、尺寸、颜色等属性。

  • 允许缺货超售?
    • 不允许
    • 允许
    • 允许,但通知客户 (客户购买后,会在邮件通知中显示”延迟交货“)

全局变量$product

1
2
3
4
5
6
7
8
9
<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $product; ?>
		<pre>
		    <!-- simple:单个产品 external:外接/关联产品 grouped:成组产品 variable:可变产品 -->
			<?php echo $product->get_type(); ?>
		</pre>
	<?php endwhile; ?>
<?php endif; ?>

产品相关数据查询

产品ID

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $product, $post; ?>
		<h2>产品ID</h2>
        <?php echo $product->get_id(); ?>
        <?php echo get_the_ID(); ?>
        <?php echo $post->ID; ?>
        <?php echo get_queried_object_id(); ?>
	<?php endwhile; ?>
<?php endif; ?>

产品图片查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 新版woocommerce没有产品图片设置页面,可以通过代码自定图片尺寸 -->
<?php

add_image_size('shop_catalog', 300, 300, true);
add_image_size('shop_single', 600, 600, true);
add_image_size('shop_thumbnail', 180, 180, true);

//新版提供了新的方法
//single、gallery_thumbnail、thumbnail
wc_get_image_size('single');
?>


<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $product; ?>
		<h2>产品图片</h2>
        <?php the_post_thumbnail('shop_catalog');//目录图片 ?>
        <?php the_post_thumbnail('shop_single');//产品详情页的产品图片 ?>
        <?php the_post_thumbnail('shop_thumbnail');//产品缩略图 ?>
	<?php endwhile; ?>
<?php endif; ?>

产品相册

1
2
3
4
5
6
7
8
<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $product; ?>
		<h2>产品相册</h2>
        <?php foreach($product->get_gallery_image_ids() as $aid): ?>
        <?php echo wp_get_attachment_image($aid, 'shop_thumbnail'); ?>
	<?php endwhile; ?>
<?php endif; ?>

产品其他信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $product; ?>
		<div>
            <h2>产品标题</h2>
            <?php echo $post->post_title; ?>
            <?php echo $product->get_name(); ?>
    	</div>
		<div>
            <h2>产品价格</h2>
            <?php echo $post->get_price_html(); ?>
    	</div>
		<div>
            <h2>产品简介</h2>
            <?php the_excerpt(); ?>
            <?php echo $product->get_short_description(); ?>
    	</div>
		<div>
            <h2>产品货号</h2>
            <?php echo $product->get_sku(); ?>
    	</div>
		<div>
            <h2>产品正文</h2>
            <?php echo $product->get_description(); ?>
            <?php the_content(); ?>
    	</div>
		<div>
            <h2>产品属性</h2>
            <?php echo wc_display_product_attributes($product); ?>
    	</div>
		<div>
            <h2>产品所属分类</h2>
            <?php echo wc_get_product_category_list($product->get_id(), '-'); ?>
    	</div>
		<div>
            <h2>产品所属标签</h2>
            <?php echo wc_get_product_tag_list($product->get_id(), '-'); ?>
    	</div>
		<div>
            <h2>加入购物车</h2>
            <?php woocommerce_template_single_add_to_cat(); ?>
    	</div>
		<div>
            <h2>增销产品</h2>
            <?php
            //$limit 显示数量 -1显示全部
            //$columns 每行显示几个
            //woocommerce_upsell_display($limit, $columns, $orderby, $order);
            
            ?>
            <?php woocommerce_upsell_display(); ?>
    	</div>
		<div>
            <h2>相关产品</h2>
            <?php 
            // orderby: rand,title,ID,date,modified,menu_order,price
            $defauls = [
                'posts_per_page' => 1,
                'columns'        => 2,
                'orderby'        => 'rand',
                'order'          => 'desc'
            ];
          	woocommerce_related_products($defauls); 
            
            ?>
    	</div>
	<?php endwhile; ?>
<?php endif; ?>

产品自定义查询示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 获取所有有货、店铺页可见 (NOT IN (outofstock, exclude-from-catalog))
 * 并且
 * 在别名为phone的产品分类中或者在别名为hot的产品标签中的
 * 所有产品
 */
$query = new WP_Query([
    'post_type' => 'product',
    'posts_per_page' => -1,
    'tax_query' => [
        'relation' => 'AND',
        [
            'taxonomy' => 'product_visibility',
            'field' => 'slug',
            'terms' => [
                'exclude-from-catalog',
                'outofstock'
            ],
            'operator' => 'NOT IN',
        ],
        [
            'relation' => 'OR',
            [
                'taxonomy' => 'product_cat',
                'field' => 'slug',
                'terms' => 'phone',
            ],
            [
                'taxonomy' => 'product_tag',
                'field' => 'slug',
                'terms' => 'hot',
            ]
        ]
    ],
]);

if ($query->have_post()) while($query->have_post()) {
    $query->the_post();
    $global $product;
    $product->get_id();
    the_ID();
}
wp_reset_postdata();

产品可见性(product_visibility)

  • exclude-from-search

所有在搜索结果中排除的产品,都在此分类项目中

  • exclude-from-catalog

所有从店铺页或分类归档页中排除的产品,都在此分类项目中

  • featured

所有推荐的产品,都在此分类项目中

  • outofstock

所有没有库存的产品,都在此分类项目中

  • rated-1

1星评价

  • rated-2

2星评价

  • rated-3

3星评价

  • rated-4

4星评价

  • rated-5

5星评价

产品可见性页面设置中对应的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 1.商店页和搜索结果页
"product_visibility NOT IN ('exclude-from-catalog', 'exclude-from-search')" 

// 2.仅商店页
"product_visibility IN ('exclude-from-search')" 

// 3.仅搜索结果页
"product_visibility IN ('exclude-from-catalog')" 

// 4.隐藏
"product_visibility IN ('exclude-from-catalog', 'exclude-from-search')" 

// 5.推荐产品
"product_visibility IN ('featured')" 

产品可见性查询示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 获取所有有货、店铺页可见 (NOT IN (outofstock, exclude-from-catalog))
 * 并且
 * 在别名为other的产品分类中的
 * 所有产品
 */
$query = new WP_Query([
    'post_type' => 'product',
    'posts_per_page' => -1,
    'tax_query' => [
        'relation' => 'AND',
        [
            'taxonomy' => 'product_visibility',
            'field' => 'slug',
            'terms' => [
                'exclude-from-catalog',
                'outofstock'
            ],
            'operator' => 'NOT IN',
        ],
        [
            'taxonomy' => 'product_cat',
            'field' => 'slug',
            'terms' => 'other',
        ]
    ],
]);

模板层级

开启主题对woocommerce支持

1
2
3
4
add_action('after_setup_theme', function () {
    // 开启woocommerce支持,后面的模板层级才生效
    add_theme_support('woocommerce');
});

产品详情页模板层级

1
2
3
4
5
woocommerce.php
$custom.php                                       //自定义模板文件
single-product.php
woocommerce/single-product.php
plugins/woocommerce/templates/single-product.php  //wcc插件中的模板文件

产品分类归档页模板层级

1
2
3
4
5
6
woocommerce.php
taxonomy-product_cat-$slug.php
woocomerce/taxonomy-product_cat-$slug.php
taxonomy-product_cat.php
woocommerce/taxonomu-product_cat.php
plugins/woocomerce/templates/taxonomy-product_cat.php

产品标签归档页模板层级

1
2
3
4
5
6
woocommerce.php
taxonomy-product_tag-$slug.php
woocommerce/taxonomy-proudct_tag-$slug.php
taxonomy-product_tag.php
woocommerce/taxonomy-product_tag.php
plugins/woocommerce/templates/taxonomy-product_tag.php

全局属性归档页模板层级

1
2
3
4
5
6
7
8
woocommerce.php
taxonomy-pa_$attr_slug-$term_slug.php
woocomerce/taxonomy-pa_$attr_slug-$term_slug.php
taxonomy-pa_$attr_slug.php
woocomerce/taxonomy-pa_$attr_slug.php
archive-product.php
woocommerce/archive-product.php
plugins/woocommerce/templates/archive-product.php

WCC创建的页面模板层级

店铺页面

1
2
3
4
woocomerce.php
archive-product.php
woocommerce/archive-product.php
plugins/woocomerce/templates/archive-product.php

我的账户页

普通页面,按照wordpress默认的模板层级规则来

结算页

普通页面,按照wordpress默认的模板层级规则来

购物车页

普通页面,按照wordpress默认的模板层级规则来

用户协议页

普通页面,按照wordpress默认的模板层级规则来

WCC条件标签

是否woocommerce页面

1
2
3
4
5
6
7
8
/**
 * Is_woocommerce - Returns true if on a page which uses WooCommerce templates (cart and checkout are standard pages with shortcodes and thus are not included).
 *
 * @return bool
 */
function is_woocommerce() {
	return apply_filters( 'is_woocommerce', is_shop() || is_product_taxonomy() || is_product() );
}

是否店铺页

1
2
3
4
5
6
7
8
/**
 * Is_shop - Returns true when viewing the product type archive (shop).
 *
 * @return bool
 */
function is_shop() {
    return ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) );
}

是否产品分类页

1
2
3
4
5
6
7
8
9
/**
 * Is_product_category - Returns true when viewing a product category.
 *
 * @param  string $term (default: '') The term slug your checking for. Leave blank to return true on any.
 * @return bool
 */
function is_product_category( $term = '' ) {
    return is_tax( 'product_cat', $term );
}

是否产品标签页

1
2
3
4
5
6
7
8
9
/**
 * Is_product_tag - Returns true when viewing a product tag.
 *
 * @param  string $term (default: '') The term slug your checking for. Leave blank to return true on any.
 * @return bool
 */
function is_product_tag( $term = '' ) {
    return is_tax( 'product_tag', $term );
}

是否产品详情页

1
2
3
4
5
6
7
8
/**
 * Is_product - Returns true when viewing a single product.
 *
 * @return bool
 */
function is_product() {
    return is_singular( array( 'product' ) );
}

是否购物车页

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * Is_cart - Returns true when viewing the cart page.
 *
 * @return bool
 */
function is_cart() {
    $page_id = wc_get_page_id( 'cart' );

    return ( $page_id && is_page( $page_id ) ) || Constants::is_defined( 'WOOCOMMERCE_CART' ) || wc_post_content_has_shortcode( 'woocommerce_cart' );
}

是否是结算页

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * Is_checkout - Returns true when viewing the checkout page, or when processing AJAX requests for updating or processing the checkout.
 *
 * @return bool
 */
function is_checkout() {
    $page_id = wc_get_page_id( 'checkout' );

    return ( $page_id && is_page( $page_id ) ) || wc_post_content_has_shortcode( 'woocommerce_checkout' ) || apply_filters( 'woocommerce_is_checkout', false ) || Constants::is_defined( 'WOOCOMMERCE_CHECKOUT' );
}

是否是账户页

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * Is_account_page - Returns true when viewing an account page.
 *
 * @return bool
 */
function is_account_page() {
    $page_id = wc_get_page_id( 'myaccount' );

    return ( $page_id && is_page( $page_id ) ) || wc_post_content_has_shortcode( 'woocommerce_my_account' ) || apply_filters( 'woocommerce_is_account_page', false );
}

是否产品相关的分类方式归档页

包括产品分类、产品标签、产品属性等

1
2
3
4
5
6
7
8
/**
 * Is_product_taxonomy - Returns true when viewing a product taxonomy archive.
 *
 * @return bool
 */
function is_product_taxonomy() {
    return is_tax( get_object_taxonomies( 'product' ) );
}

更多条件标签

所有条件标签查看:plugins/woocommerce/includes/wc-conditional-functions.php

WCC短代码

短代码的执行

1
2
3
4
5
6
7
<?php if(have_posts()): ?>
	<?php while(have_post()):the_post(); ?>
		<?php global $post; ?>
        <?php echo do_shortcode($post->content); ?>
        <?php echo the_content();//the_content()里面自动解析了短代码 ?>
	<?php endwhile; ?>
<?php endif; ?>

购物车页面短代码

[woocommerce_cart]

plugins/woocommerce/includes/class-wc-shortcodes.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class WC_Shortcodes {
    
    /**
	 * Cart page shortcode.
	 *
	 * @return string
	 */
	public static function cart() {
		return is_null( WC()->cart ) ? '' : self::shortcode_wrapper( array( 'WC_Shortcode_Cart', 'output' ) );
	}
    
}

结算页面短代码

[woocommerce_checkout]

plugins/woocommerce/includes/class-wc-shortcodes.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class WC_Shortcodes {
    
    /**
	 * Checkout page shortcode.
	 *
	 * @param array $atts Attributes.
	 * @return string
	 */
	public static function checkout( $atts ) {
		return self::shortcode_wrapper( array( 'WC_Shortcode_Checkout', 'output' ), $atts );
	}
    
}

账户页面短代码

[woocommerce_my_account]

plugins/woocommerce/includes/class-wc-shortcodes.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class WC_Shortcodes {
    
    /**
	 * My account page shortcode.
	 *
	 * @param array $atts Attributes.
	 * @return string
	 */
	public static function my_account( $atts ) {
		return self::shortcode_wrapper( array( 'WC_Shortcode_My_Account', 'output' ), $atts );
	}
    
}
输出notices
1
2
3
4
//打印所有notices
wc_print_notices();
//添加notice
wc_add_notice('hello world');

WCC主题开发

样式和脚本

1
2
3
4
5
6
// 1. 通过wp_register_script()和wp_register_style(),先注册脚本和样式
wp_register_script( $handle, $path, $deps, $version, $in_footer );
wp_register_style( $handle, $path, $deps, $version, $media );

// 2. 通过wp_enqueue_script($handle)和wp_enqueue_style($handle),调用已经注册好的脚本和样式
wp_enqueue_script( 'wc-add-to-cart-variation' );

移除WCC自带的样式和脚本

1
2
3
4
5
// 去除wcc自带的样式,__return_empty_array是wp自带的函数,返回空数组
add_filter('woocommerce_enqueue_styles', '__return_empty_array');

// 去除wcc自带的样式和脚本,load_scripts 里面执行了woocommerce_enqueue_styles过滤器钩子
remove_action( 'wp_enqueue_scripts', [ WC_Frontend_Scripts::class, 'load_scripts' ] );

声明支持WCC

1
2
3
4
add_action('after_setup_theme', function () {
    //声明支持woocommerce
    add_theme_support('woocommerce');
});

自主式主题开发

自己根据WCC的模板层级,通过自定义的查询和自定义CSS、js的方式实现自己的主题

覆盖式主题开发

在开发兼容WCC的主题时,首先考虑的不是要自己去写所有的模板,而是在WCC模板的基础上去修改。比如,去修改数据的输出结构等。把plugins/woocommerce/templates的指定的模板文件复制到当前主题/woocommerce目录下

重要函数

  • wc_get_template定义
1
2
3
4
5
6
7
8
9
/**
 * Get other templates (e.g. product attributes) passing attributes and including the file.
 *
 * @param string $template_name Template name.
 * @param array  $args          Arguments. (default: array).
 * @param string $template_path Template path. (default: '').
 * @param string $default_path  Default path. (default: '').
 */
function wc_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' )
  • wc_get_template使用举例
1
2
3
4
5
6
wc_get_template('cart/cart-item-data.php', [
    'item_data' => $item_data
]);

//1. 先在当前主题的woocommerce目录中查找
//2. 然后再到plugins/woocommerce/templates目录中查找
  • wc_get_template_part定义
1
2
3
4
5
6
7
8
9
/**
 * Get template part (for templates like the shop-loop).
 *
 * WC_TEMPLATE_DEBUG_MODE will prevent overrides in themes from taking priority.
 *
 * @param mixed  $slug Template slug.
 * @param string $name Template name (default: '').
 */
function wc_get_template_part( $slug, $name = '' )
  • wc_get_template_part举例
1
2
3
4
5
6
7
8
// 查找shop-product.php文件
wc_get_template_part('shop', 'product');

//1. 在当前主题目录中查找$slug-$name.php文件
//2. 在当前主题的woocommerce目录查找$slug-$name.php文件
//3. 在plugins/woocommerce/templates目录中查找$slug-$name.php
//4. 在当前主题的woocommerce目录查找$slug.php
//5. 在plugins/woocommerce/templates目录中查找$slug.php

主题开发要点串联

制作详情页顶部导航

  • 登录链接
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/**
 * 获取当前访问页面的url的方法
 * 在主题的function.php定义,在模板文件中使用
 */
function get_curr_url() {
    global $wp;
    return get_home_url(null, $wp->request);
}


// 前台使用
$curr_url = get_curr_url();
$login_url = wp_login_url($curr_url);
<a href="<?php echo $login_url; ?>">登录</a>
  • 用户中心链接
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * $pagename可选的值,如下:
 * myaccount  我的账户页面
 * shop  店铺
 * cart  购物车
 * checkout  结算
 * terms  条款
 */
wc_get_page_permalink($pagename);
    
//我的账户页面
wc_get_page_permalink('myacount');
//购物车页面
wc_get_page_permalink('cart');

//我的账户页面的端点
//订单 orders
//查看订单  view-order
//下载  downloads
//编辑账户 edit-address
//地址 edit-address

商品搜索框

1
2
3
4
get_product_search_form($echo = true);

//复制woocommerce/templates/product-searchform.php到当前主题的woocommerce目录下
//修改product-searchform.php内容完成定制

面包屑导航

1
2
3
4
5
6
7
8
9
$args = [
    'delimiter'   => '&nbsp;&#47;&nbsp;',
    'wrap_before' => '<nav class="woocommerce-breadcrumb" aria-label="Breadcrumb">',
    'wrap_after'  => '</nav>',
    'before'      => '',
    'after'       => '',
    'home'        => '站点首页',
];
woocommerce_breadcrumb($args);

获取产品信息

  • 产品图片
1
the_post_thumbnail_url('shop_single');
  • 产品价格
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//格式化价格 $16.90
wc_price($price);

//获取产品价格,可以设定数量和自定义价格
//$args可选参数,如不传递则返回一件产品的实际价格。如传递格式为['price' => 123, 'qty' => 2],意思是:获取2件产品的价格,每件为123元。
wc_get_price_to_display($product, $args = []);

//产品原价
$product->get_regular_price();

//产品促销价格
$product->get_sale_price();

//产品实际价格,如果有设置促销价格就显示促销价格,否则显示产原价
$product->get_price();

//综合应用
wc_price(wc_get_price_to_display($product));
  • 获取产品重量
1
2
3
4
5
6
7
8
9
/**
 * @param $weigth 重量的数值
 * @param $to_unit 转为什么单位 可取的值为 'g','kg','lbs','oz'
 * @param $from_unit 转换前的单位,可选参数。不传递会自动获取WCC设置的默认重量单位
 */
wc_get_weight($weight, $to_unit, $from_unit = '');

//应用举例
wc_get_weight($product->get_weight(), 'g');
  • 获取商品评论信息
1
2
3
// 该方法调用的是woocommerce/templates/single-product-reviews.php
// 可以把single-product-reviews.php复制到当前主题woocommerce目录下
comments_template();

加入购物车

wcc默认方式

1
2
3
4
// 表单方式
woocommerce_template_single_add_to_cart();
// ajax方式
woocommerce_template_loop_add_to_cart();

自定义ajax方式

关键文件plugins/woocommerce/includes/class-wc-ajax.php

do_action( 'wc_ajax_' . $action )根据$action参数动态触发钩子,可以用于自定义?wc-ajax=自定义动作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Check for WC Ajax request and fire action.
 */
public static function do_wc_ajax() {
    global $wp_query;

    // phpcs:disable WordPress.Security.NonceVerification.Recommended
    if ( ! empty( $_GET['wc-ajax'] ) ) {
        $wp_query->set( 'wc-ajax', sanitize_text_field( wp_unslash( $_GET['wc-ajax'] ) ) );
    }

    $action = $wp_query->get( 'wc-ajax' );

    if ( $action ) {
        self::wc_ajax_headers();
        $action = sanitize_text_field( $action );
        do_action( 'wc_ajax_' . $action );
        wp_die();
    }
    // phpcs:enable
}
  • ajax方式加入购物车
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- WCC默认会向商品页面中增加JS脚本,这些脚本给我们提供了一些重要的数据。-->
<script>
    console.log(wc_add_to_cart_params)
    // 获取加入购物车的url
	var ajax_url = wc_add_to_cart_params.wc_ajax_url.toString().replace('%%endpoint%%', 'add_to_cart');
    
    // 通过ajax把数据提交到ajax_url
    var params = {'product_id': 100, 'quantity': 2};
    // $.post()发起请求
    // 获取response数据
	if (!response) {
    	return ;    
    }
    
    //返回的数据中标明有错误时
    if (response.error && response.product_url) {
        window.location = response.product_url;
        return ;
    }
    
    //如果后台设置了加入购物车后就跳转到购物车页面时
    if (wc_add_to_cart_params.cart_redirect_after_add == 'yes') {
     window.location = wc_add_to_cart_params.cart_url;
        return ;
    }
</script>
  • 通过钩子woocommerce_add_to_cart_fragments扩展购物车产品数量字段
1
2
3
4
add_filter('woocommerce_add_to_cart_fragments', function ($return) {
    $return['qty'] = WC()->cart->get_cart_contents_count();
    return $return;
});

获取购物车中的产品数量

1
WC()->cart->get_cart_contents_count();

计算商品总价格

通过class-wc-ajax.php中do_action( 'wc_ajax_' . $action ) 自定义一个action,如?wc-ajax=get_product_total_price

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//在functions.php文件中添加以下方法
add_action('wc_ajax_get_product_total_price', function () {
    $product_id = $_POST['product_id'];
    $count = $_POST['count'];
    $product = wc_get_product($product_id);
    $total = wc_get_price_to_display($product, [
        'qty' => $count
    ]);
    
    $total = wc_price($total);
    
    wp_send_json([
        'total' => $total
    ]); 
})

列表分页

1
woocommerce_pagination();

产品排序

1
woocommerce_catalog_ordering();

获取指定产品分类数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$term_id = 19;
/* @var $term WP_Term */
$term = get_term($term_id, 'product_cat');
$term->name;
$term->slug;
$term->description;
$term_url = get_term_link($term);
//获取<img></img>格式的数据
woocommerce_subcategory_thumbnail($term);
//获取图片url
$term_thumbnail_id = get_term_meta($term_id, 'thumbnail_id', true);
$term_thumbnail_url = wp_get_attachment_image_url($term_thumbnail_id, 'thumbnail');

//通过自定义查询获取分类下的产品
$query = new WP_Query([
    'post_type' => 'product',
    'posts_per_page' => 4,
    'tax_query' => [
        'ralation' => 'AND',
        [
            'taxonomy' => 'product_visibility',
            'field' => 'slug',
            'terms' => ['exculde-from-catalog', 'outofstock'],
            'operator' => 'NOT IN'
        ],
        [
            'taxonomy' => 'product_cat',
            'field' => 'slug',
            'terms' => ['shuiguo']
        ]
    ]
]);

while ($query->have_posts()) {
    $query->the_post();
    //获取产品具体信息
}

WCC支付网关

woocommerce下单流程关键代码

  • 流程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
woocommerce/includes/class-wc-ajax.php
    do_wc_ajax() ===> do_action( 'wc_ajax_' . $action )
    // 结算
    checkout() ===> WC()->checkout()->process_checkout()
    
woocommerce/includes/class-wc-checkout.php
    // 下单
    process_checkout() ===> $this->process_order_payment( $order_id, $posted_data['payment_method'] )
    // 获取支付网关中指定的支付方式,执行该支付方式的process_payment()方法
    process_order_payment() ===> $available_gateways[ $payment_method ]->process_payment( $order_id )
  • woocommerce/includes/class-wc-ajax.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Check for WC Ajax request and fire action.
 */
public static function do_wc_ajax() {
    global $wp_query;

    // phpcs:disable WordPress.Security.NonceVerification.Recommended
    if ( ! empty( $_GET['wc-ajax'] ) ) {
        $wp_query->set( 'wc-ajax', sanitize_text_field( wp_unslash( $_GET['wc-ajax'] ) ) );
    }

    $action = $wp_query->get( 'wc-ajax' );

    if ( $action ) {
        self::wc_ajax_headers();
        $action = sanitize_text_field( $action );
        do_action( 'wc_ajax_' . $action );//关键代码
        wp_die();
    }
    // phpcs:enable
}

/**
 * Process ajax checkout form.
 */
public static function checkout() {
    wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
    WC()->checkout()->process_checkout();//关键代码
    wp_die( 0 );
}
  • woocommerce/includes/class-wc-checkout.php
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * Process the checkout after the confirm order button is pressed.
 *
 * @throws Exception When validation fails.
 */
public function process_checkout() {
    try {
        $nonce_value    = wc_get_var( $_REQUEST['woocommerce-process-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // phpcs:ignore
        $expiry_message = sprintf(
            /* translators: %s: shop cart url */
            __( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'woocommerce' ),
            esc_url( wc_get_page_permalink( 'shop' ) )
        );

        if ( empty( $nonce_value ) || ! wp_verify_nonce( $nonce_value, 'woocommerce-process_checkout' ) ) {
            // If the cart is empty, the nonce check failed because of session expiry.
            if ( WC()->cart->is_empty() ) {
                throw new Exception( $expiry_message );
            }

            WC()->session->set( 'refresh_totals', true );
            throw new Exception( __( 'We were unable to process your order, please try again.', 'woocommerce' ) );
        }

        wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
        wc_set_time_limit( 0 );

        do_action( 'woocommerce_before_checkout_process' );

        if ( WC()->cart->is_empty() ) {
            throw new Exception( $expiry_message );
        }

        do_action( 'woocommerce_checkout_process' );

        $errors      = new WP_Error();
        $posted_data = $this->get_posted_data();

        // Update session for customer and totals.
        $this->update_session( $posted_data );

        // Validate posted data and cart items before proceeding.
        $this->validate_checkout( $posted_data, $errors );

        foreach ( $errors->errors as $code => $messages ) {
            $data = $errors->get_error_data( $code );
            foreach ( $messages as $message ) {
                wc_add_notice( $message, 'error', $data );
            }
        }

        if ( empty( $posted_data['woocommerce_checkout_update_totals'] ) && 0 === wc_notice_count( 'error' ) ) {
            $this->process_customer( $posted_data );
            $order_id = $this->create_order( $posted_data );
            $order    = wc_get_order( $order_id );

            if ( is_wp_error( $order_id ) ) {
                throw new Exception( $order_id->get_error_message() );
            }

            if ( ! $order ) {
                throw new Exception( __( 'Unable to create order.', 'woocommerce' ) );
            }

            do_action( 'woocommerce_checkout_order_processed', $order_id, $posted_data, $order );

            /**
				 * Note that woocommerce_cart_needs_payment is only used in
				 * WC_Checkout::process_checkout() to keep backwards compatibility.
				 * Use woocommerce_order_needs_payment instead.
				 *
				 * Note that at this point you can't rely on the Cart Object anymore,
				 * since it could be empty see:
				 * https://github.com/woocommerce/woocommerce/issues/24631
				 */
            if ( apply_filters( 'woocommerce_cart_needs_payment', $order->needs_payment(), WC()->cart ) ) {
                $this->process_order_payment( $order_id, $posted_data['payment_method'] );//关键代码
            } else {
                $this->process_order_without_payment( $order_id );
            }
        }
    } catch ( Exception $e ) {
        wc_add_notice( $e->getMessage(), 'error' );
    }
    $this->send_ajax_failure_response();
}


/**
 * Process an order that does require payment.
 *
 * @since 3.0.0
 * @param int    $order_id       Order ID.
 * @param string $payment_method Payment method.
 */
protected function process_order_payment( $order_id, $payment_method ) {
    $available_gateways = WC()->payment_gateways->get_available_payment_gateways();

    if ( ! isset( $available_gateways[ $payment_method ] ) ) {
        return;
    }

    // Store Order ID in session so it can be re-used after payment failure.
    WC()->session->set( 'order_awaiting_payment', $order_id );

    // We save the session early because if the payment gateway hangs
    // the request will never finish, thus the session data will neved be saved,
    // and this can lead to duplicate orders if the user submits the order again.
    WC()->session->save_data();

    // Process Payment.
    $result = $available_gateways[ $payment_method ]->process_payment( $order_id );//关键代码

    // Redirect to success/confirmation/payment page.
    if ( isset( $result['result'] ) && 'success' === $result['result'] ) {
        $result['order_id'] = $order_id;

        $result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id );

        if ( ! wp_doing_ajax() ) {
            // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
            wp_redirect( $result['redirect'] );
            exit;
        }

        wp_send_json( $result );
    }
}

判断woocommerce是否启用

1
2
3
if (!in_array('woocommerce/woocommerce.php', get_option('active_plugins'))) {
    return ;
}

告诉woocommerce新增的网关

1
2
3
4
add_filter('woocommerce_payment_gateways', function ($methods) {
    $methods[] = 'WC_Gateway_Alipay';
    return $methods;
});

必须调用的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//在新增的网关类中调用$this->init_form_fields()、$this->init_settings()

// 实现init_form_fields方法
// 可以参考woocommerce/includes/gateways/paypal/settings-paypal.php
function init_form_fields() {
    $this->form_fields = [
        //是否启用当前的支付网关
        'enabled' => [
            'title'    => '启用/禁用',
            'type'     => 'checkbox',
            'label'    => '是否启用支付宝网关,勾选为启用',
            'default'  => 'yes', 
        ],
        //当前支付网关的名称
        'title' => [
            'title'       => '标题',
            'type'        => 'text',
            'description' => '在结算时看到的当前支付网关的名称',
            'default'     => '支付宝',
            'desc_tip'    => true,
        ],
        //当前支付网关的描述
        'description' => [
            'titile'      => '描述',
            'type'        => 'text',
            'desc_tip'    => true,
            'description' => '在结算时看到的当前支付网关的描述',
            'default'     => '将使用支付宝付款',
        ],
        //以上三个字段是必须定义的
        //下面可以定义一些当前支付网关的字段,如:app_id, mch_id
        'app_id' => [
            'titile'      => 'app_id',
            'type'        => 'text',
            'desc_tip'    => true,
            'description' => '支付宝APP ID',
            'default'     => '',
        ],
        'mch_id' => [
            'titile'      => 'mch_id',
            'type'        => 'text',
            'desc_tip'    => true,
            'description' => '支付宝商户ID',
            'default'     => '',
        ],
    ];
}

保存后台设置的数据

1
2
//在新增的网关类中挂载以下钩子
add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']);

异步回调

  • 生成异步回调链接
1
2
3
4
5
//异步通知地址。生成一个URL:域名/wc-api/WC_Gateway_Awxpay
$this->notify_url = WC()->api_request_url('WC_Gateway_Awxpay');

//异步退款通知地址。生成一个URL:域名/wc-api/WC_Gateway_Awxpay_Refund
$this->refund_notify_url = WC()->api_request_url('WC_Gateway_Awxpay_Refund');
  • 挂载关键钩子
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//支付异步回调通知
//在plugins/woocommerce/includes/class-wc-api.php中的handle_api_requests()方法,处理了异步回调的请求,并且触发了对应的钩子。do_action( 'woocommerce_api_request', $api_request )和do_action( 'woocommerce_api_' . $api_request );;

add_action('woocommerce_api_wc_gateway_awxpay',array($this,'check_response'));

//退款异步回调通知
add_action('woocommerce_api_wc_gateway_awxpay_refund', array($this, 'refund_response'));

//订单完成时触发
add_action("woocommerce_order_status_completed",array($this, 'refund_order_trial_product'));
使用 Hugo 构建
主题 StackJimmy 设计