WP笔记

WooCommerce发邮件的逻辑

WooCommerce的邮件发送逻辑历经版本已经发生了很多变化,所以决定更新这篇过时的文章,记录一下WooCommerce 7.8.1里发送邮件的逻辑,以及如何用代码发送任意邮件,如何延迟邮件的发送。

WooCommerce邮件发送过程

用【文件名+相关代码】的方式描述,以order complete订单为例,代码都可以在对应文件里找到。

(A) class-wc-order.php

  1. $order->save()
  2. $this->status_transition()
  3. do_action( ‘woocommerce_order_status_’ . $status_transition[‘to’], $this->get_id(), $this );

(B) class-woocommerce.php

  1. add_action( ‘init’, array( ‘WC_Emails’, ‘init_transactional_emails’ ) );

(C) class-wc-emails.php

  1. do_action( $action, array( __CLASS__, ‘send_transactional_email’ ), 10, 10 );
  2. do_action_ref_array( current_filter() . ‘_notification’, $args );

(D) class-wc-email-customer-completed-order.php

  1. add_action( ‘woocommerce_order_status_completed_notification’, array( $this, ‘trigger’ ), 10, 2 );

文字版描述:

(A) 当用户保存订单、通过REST API保存订单或用代码保存订单($order->save())时,执行status_transition()函数,该函数会对比订单之前的状态和更新后的状态,比如更新前是pending,更新后是completed,就执行do_action('woocommerce_order_status_completed',$order_id, $order),把订单完成这个状态广播给所有订阅了它的组件。

(B)init_transactional_emails()class-wc-emails.php里定义的函数,负责维护一个$email_actions数组,它记录了所有email需要关注的actions,然后通过add_action来订阅这些action的变化,当变化发生时,通过函数send_transactional_email()queue_transactional_email()来做对应处理。

(C)调用send_transactional_email()函数,用当前的action名称创建一个新的action,叫woocommerce_order_status_completed_notification,负责发送email的组件订阅可以订阅它,这样当这个action触发时,email组件就知道自己要发邮件了。

(D)最后来到负责发送order complete邮件的组件,它接收到woocommerce_order_status_completed_notification这个action的通知后,就调用自己的trigger()方法,这个方法最终调用WC_Email的send()函数发送邮件。

如何用程序发送WC Order Complete邮件

只需要用对应的组件发送即可,例如定义一个发送order complete邮件的函数,并以order ID作为参数

function send_order_complete_email($order_id) {
	add_filter('woocommerce_email_enabled_customer_completed_order', '__return_true');
	$email_oc = new WC_Email_Customer_Completed_Order();
	$email_oc->trigger($order_id);
}

第一句add_filter是保证即使后台关闭了这封邮件,也可以发送,因为trigger()函数只在后台开启了对应邮件时才能发送。这个filter定义在class-wc-email.phpis_enabled()函数里。

其他关于邮件的代码段

发送一封WC风格的自定义邮件,参数分别为:收件人地址,邮件标题,WooCommerce邮件header里的标题,邮件正文。

function send_email_woocommerce_style($email, $subject, $heading, $message) {

	// Get woocommerce mailer from instance
	$mailer = WC()->mailer();

	// Wrap message using woocommerce html email template
	$wrapped_message = $mailer->wrap_message($heading, $message);

	// Create new WC_Email instance
	$wc_email = new WC_Email;

	// Style the wrapped message with woocommerce inline styles
	$html_message = $wc_email->style_inline($wrapped_message);

	// Send the email using wordpress mail function
	$mailer->send($email, $subject, $html_message, $wc_email->get_headers());
}

列出所有woocommerce emails

function list_woocommerce_emails(){
	$mailer = WC()->mailer();
	$mails = $mailer->get_emails();
	foreach($mails as $mail ){
		echo esc_html($mail->title), '<br>';
	}
}

延迟发送WooCommerce Order Complete邮件

1.

WooCommerce支持及时发送邮件或者延时发送,用户结算后需要等待邮件发送成功才能到达thank you页面,如果邮件发的慢就会影响用户体验,使用延时发送可以解决这个问题,只需要一行代码。

add_filter( 'woocommerce_defer_transactional_emails', '__return_true' );

2.

如果你想让不同邮件有不同的延时时间,参考这篇文章

3.

下面这个例子是将order complete邮件延迟两分钟发送,使用WooCommerce Action Scheduler,和2的区别是没有使用WC的延时系统,只是取消了默认的邮件发送,再在适当的时候手动发送邮件。

class Defer_WooCommerce_Order_Completed_Email {
	private static $instance;
	private $default_defer_time;

	public static function get_instance() {
		if (null == self::$instance) {
			self::$instance = new Defer_WooCommerce_Order_Completed_Email();
		}
		return self::$instance;
	}

	public function __construct() {
		$this->default_defer_time = 120; // 延迟120s,2分钟
		add_filter('woocommerce_email_enabled_customer_completed_order', '__return_false');
		add_action('woocommerce_order_status_completed_notification', [$this, 'schedule_email'], 10, 2);
		add_action('send_deferred_order_completed_email', [$this, 'send_email']);
	}

	function schedule_email($order_id, $order = false) {
		$event_id = as_schedule_single_action(time() + $this->default_defer_time, 'send_deferred_order_completed_email', array($order_id));
	}

	function send_email($order_id) {
		add_filter('woocommerce_email_enabled_customer_completed_order', '__return_true');
		WC()->mailer()->emails['WC_Email_Customer_Completed_Order']->trigger( $order_id );
	}
}
new Defer_WooCommerce_Order_Completed_Email();

用WC REST API更改订单状态

测试订单邮件时使用REST API更改订单状态也是很方便的,方法也很简单。

  1. 打开WooCommerce->Settings->Advanced->REST API,点击Add Key并填入内容,权限选择Read/Write,这样就创建了一个有读写权限的API用户,创建好把用户名和密码(Consumer key & Consumer secret)记录下来。
  2. 从WC REST API文档中找到Update an Order,从中获得rest api地址,/wp-json/wc/v3/orders/<id>
  3. 打开Postman,先打开Authorization选项卡,Type选择Basic Auth,然后填上刚才创建的用户名和密码。
  4. 在Postman地址栏输入REST API地址,方法选择PUT,Params里天上{"status":"completed"},点击Send发送请求,就能将订单状态更新成completed。
solagirl.net:postman wc rest api auth info
solagirl: wc rest api update an order url and params

用python更新订单状态

import requests
import base64

wordpress_user = "ck_29990e5f3e3053a727a75f9c1bcd7420b764d62e"
wordpress_password = "cs_b4c1c3eaf8ff36102bac0e425afbbd6971097c56"
rest_base = 'https://testwc.local/wp-json';
wordpress_credentials = wordpress_user + ":" + wordpress_password
wordpress_token = base64.b64encode(wordpress_credentials.encode())
wordpress_header = {'Authorization': 'Basic ' + wordpress_token.decode('utf-8')}

def update_wc_order(order_id, data):
    api_url = f'{rest_base}/wc/v3/orders/{order_id}'
    r = requests.put(api_url,headers=wordpress_header, json=data)
    return r.json()


if __name__ == '__main__':
    update_wc_order(65, {
        'status': 'processing'
    })

可以用VS Code快速开始一个python项目,步骤如下,参考https://code.visualstudio.com/docs/python/python-tutorial

  1. 保证系统安装了python。
  2. 用VS Code打开一个文件夹,快捷键Ctrl+Shift+P打开命令面板,输入Python: Create Environment,选择虚拟环境venv,再根据提示选择解释器。
  3. 创建一个python文件,例如order.py,将代码拷贝进去。
  4. 安装需要的模块,运行命令pip install requests,最后运行文件即可。

23条评论

  1. 不知道博主还在不,想问一个问题,就是我的woo在用户使用paypal支付成功以后,订单状态还是不变,依旧是未支付,要手动去改变才行

    1. paypal的状态需要通过接受伊布通知来实现,可能是你服务器配置有问题,或者拦截了来自paypal的请求。
      去woocommerce的系统工具里看看,里面会显示必须安装的服务是否装了,如果没有装全就用不了paypal。

  2. /?add-to-cart=28 是添加 ID 为 28 的商品到购物车的URL参数,那么能否同时添加两个商品呢?比如 ID 为 28 和 26 的这两个商品。

        1. 嗯,在群里看了看这只猪你发了好多次,真是霸气侧漏。我家猪跟他比太瘦了,我得多喂喂,猪还是肥肥的好看。

        1. 嗯,使用minify插件时很容易出这个问题,因为压缩后可能更改js脚本加载顺序,导致某些脚本无法工作。

          可以试试把验证码相关的脚本从minify里排除,可能就好了。