diff --git a/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.py b/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..96f4c78cb96145c002ac147d2f8ab68bb8ae8e65
--- /dev/null
+++ b/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.py
@@ -0,0 +1,330 @@
+# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
+from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort
+
+from DateTime import DateTime
+from Products.ERP5Type.tests.utils import createZODBPythonScript
+import difflib
+
+HARDCODED_PRICE = 99.6
+
+vads_url_cancel = 'http://example.org/cancel'
+vads_url_error = 'http://example.org/error'
+vads_url_referral = 'http://example.org/referral'
+vads_url_refused = 'http://example.org/refused'
+vads_url_success = 'http://example.org/success'
+vads_url_return = 'http://example.org/return'
+
+class TestSlapOSWechatInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
+
+  def _simulatePaymentTransaction_getTotalPayablePrice(self):
+    script_name = 'PaymentTransaction_getTotalPayablePrice'
+    if script_name in self.portal.portal_skins.custom.objectIds():
+      raise ValueError('Precondition failed: %s exists in custom' % script_name)
+    createZODBPythonScript(self.portal.portal_skins.custom,
+                        script_name,
+                        '*args, **kwargs',
+                        '# Script body\nreturn %f' % HARDCODED_PRICE)
+
+  def _dropPaymentTransaction_getTotalPayablePrice(self):
+    script_name = 'PaymentTransaction_getTotalPayablePrice'
+    if script_name in self.portal.portal_skins.custom.objectIds():
+      self.portal.portal_skins.custom.manage_delObjects(script_name)
+
+  def test_generateManualPaymentPage_mandatoryParameters(self):
+    event = self.createWechatEvent()
+    # vads_url_cancel
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+    # vads_url_error
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+    # vads_url_referral
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+    # vads_url_refused
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+    # vads_url_success
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_return=vads_url_return,
+    )
+    # vads_url_return
+    self.assertRaises(TypeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+    )
+
+  def test_generateManualPaymentPage_noAccountingTransaction(self):
+    event = self.createWechatEvent()
+    self.assertRaises(AttributeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+
+  def test_generateManualPaymentPage_registeredTransaction(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(destination_value=payment)
+    _ , _ = payment.PaymentTransaction_generateWechatId()
+    self.assertRaises(ValueError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+
+  def test_generateManualPaymentPage_noPaymentService(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(destination_value=payment)
+    self.assertRaises(AttributeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+
+  def test_generateManualPaymentPage_noCurrency(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(
+      destination_value=payment,
+      source="portal_secure_payments/slapos_wechat_test",
+    )
+    self.assertRaises(AttributeError, event.generateManualPaymentPage,
+      vads_url_cancel=vads_url_cancel,
+      vads_url_error=vads_url_error,
+      vads_url_referral=vads_url_referral,
+      vads_url_refused=vads_url_refused,
+      vads_url_success=vads_url_success,
+      vads_url_return=vads_url_return,
+    )
+
+  def test_generateManualPaymentPage_defaultUseCase(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    payment.edit(
+      resource="currency_module/EUR",
+    )
+    event.edit(
+      destination_value=payment,
+      source="portal_secure_payments/slapos_wechat_test",
+    )
+
+    before_date = DateTime()
+    self._simulatePaymentTransaction_getTotalPayablePrice()
+    try:
+      event.generateManualPaymentPage(
+        vads_url_cancel=vads_url_cancel,
+        vads_url_error=vads_url_error,
+        vads_url_referral=vads_url_referral,
+        vads_url_refused=vads_url_refused,
+        vads_url_success=vads_url_success,
+        vads_url_return=vads_url_return,
+      )
+    finally:
+      self._dropPaymentTransaction_getTotalPayablePrice()
+    after_date = DateTime()
+
+    # Payment start date is modified
+    self.assertTrue(payment.getStartDate() >= before_date)
+    self.assertTrue(payment.getStopDate() <= after_date)
+
+    # Payment is registered
+    transaction_date, transaction_id = \
+      payment.PaymentTransaction_getWechatId()
+    self.assertNotEqual(transaction_date, None)
+    self.assertNotEqual(transaction_id, None)
+
+    # Event state
+    self.assertEqual(event.getValidationState(), "acknowledged")
+
+    data_dict = {
+      'vads_language': 'en',
+      'vads_url_cancel': vads_url_cancel,
+      'vads_url_error': vads_url_error,
+      'vads_url_referral': vads_url_referral,
+      'vads_url_refused': vads_url_refused,
+      'vads_url_success': vads_url_success,
+      'vads_url_return': vads_url_return,
+      'vads_trans_date': payment.getStartDate().toZone('UTC')\
+                           .asdatetime().strftime('%Y%m%d%H%M%S'),
+      'vads_amount': str(int(HARDCODED_PRICE * -100)),
+      'vads_currency': 978,
+      'vads_trans_id': transaction_id,
+      'vads_site_id': 'foo',
+    }
+    # Calculate the signature...
+    self.portal.portal_secure_payments.slapos_wechat_test._getFieldList(data_dict)
+    data_dict['action'] = 'https://secure.wechat.eu/vads-payment/'
+
+    expected_html_page = \
+      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w'\
+      '3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html xmlns="http://www.w3.or'\
+      'g/1999/xhtml" xml:lang="en" lang="en">\n<head>\n  <meta http-equiv="Co'\
+      'ntent-Type" content="text/html; charset=utf-8" />\n  <meta http-equiv='\
+      '"Content-Script-Type" content="text/javascript" />\n  <meta http-equiv'\
+      '="Content-Style-Type" content="text/css" />\n  <title>title</title>\n<'\
+      '/head>\n<body onload="document.payment.submit();">\n<form method="POST'\
+      '" id="payment" name="payment"\n      action="%(action)s">\n\n  <input '\
+      'type="hidden" name="vads_url_return"\n         value="'\
+      '%(vads_url_return)s">\n\n\n  <input type="hidden" name="vads_site_id" '\
+      'value="%(vads_site_id)s">\n\n\n  <input type="hidden" name="vads_url_e'\
+      'rror"\n         value="%(vads_url_error)s">\n\n\n  <input type="hidden'\
+      '" name="vads_trans_id" value="%(vads_trans_id)s">\n\n\n  <input type="'\
+      'hidden" name="vads_action_mode"\n         value="INTERACTIVE">\n\n\n  '\
+      '<input type="hidden" name="vads_url_success"\n         value="'\
+      '%(vads_url_success)s">\n\n\n  <input type="hidden" name="vads_url_refe'\
+      'rral"\n         value="%(vads_url_referral)s">\n\n\n  <input type="hid'\
+      'den" name="vads_page_action"\n         value="PAYMENT">\n\n\n  <input '\
+      'type="hidden" name="vads_trans_date"\n         value="'\
+      '%(vads_trans_date)s">\n\n\n  <input type="hidden" name="vads_url_refus'\
+      'ed"\n         value="%(vads_url_refused)s">\n\n\n  <input type="hidden'\
+      '" name="vads_url_cancel"\n         value="%(vads_url_cancel)s">\n\n\n '\
+      ' <input type="hidden" name="vads_ctx_mode" value="TEST">\n\n\n  <input '\
+      'type="hidden" name="vads_payment_config"\n         value="SINGLE">\n\n'\
+      '\n  <input type="hidden" name="vads_contrib" value="ERP5">\n\n\n  <inp'\
+      'ut type="hidden" name="signature"\n         value="%(signature)s">\n\n'\
+      '\n  <input type="hidden" name="vads_language" value="%(vads_language)s">\n\n\n  <inpu'\
+      't type="hidden" name="vads_currency" value="%(vads_currency)s">\n\n\n '\
+      ' <input type="hidden" name="vads_amount" value="%(vads_amount)s">\n\n\n'\
+      '  <input type="hidden" name="vads_version" value="V2">\n\n<input type="s'\
+      'ubmit" value="Click to pay">\n</form>\n</body>\n</html>' % data_dict
+
+    # Event message state
+    event_message_list = event.contentValues(portal_type="Wechat Event Message")
+    self.assertEqual(len(event_message_list), 1)
+    message = event_message_list[0]
+    self.assertEqual(message.getTitle(), 'Shown Page')
+    self.assertEqual(message.getTextContent(), expected_html_page,
+      '\n'.join([q for q in difflib.unified_diff(expected_html_page.split('\n'),
+        message.getTextContent().split('\n'))]))
+
+  def test_updateStatus_noAccountingTransaction(self):
+    event = self.createWechatEvent()
+    self.assertRaises(AttributeError, event.updateStatus)
+
+  def test_updateStatus_notRegisteredTransaction(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(
+      destination_value=payment,
+    )
+    self.assertRaises(ValueError, event.updateStatus)
+
+  def test_updateStatus_noPaymentService(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(
+      destination_value=payment,
+    )
+    _ , _ = payment.PaymentTransaction_generateWechatId()
+    self.assertRaises(AttributeError, event.updateStatus)
+
+  def mockSoapGetInfo(self, method_to_call, expected_args, result_tuple):
+    payment_service = self.portal.portal_secure_payments.slapos_wechat_test
+    def mocksoad_getInfo(arg1, arg2):
+      self.assertEqual(arg1, expected_args[0])
+      self.assertEqual(arg2, expected_args[1])
+      return result_tuple
+    setattr(payment_service, 'soap_getInfo', mocksoad_getInfo)
+    try:
+      return method_to_call()
+    finally:
+      del payment_service.soap_getInfo
+
+  def _simulateWechatEvent_processUpdate(self):
+    script_name = 'WechatEvent_processUpdate'
+    if script_name in self.portal.portal_skins.custom.objectIds():
+      raise ValueError('Precondition failed: %s exists in custom' % script_name)
+    createZODBPythonScript(self.portal.portal_skins.custom,
+                        script_name,
+                        '*args, **kwargs',
+                        '# Script body\n'
+"""portal_workflow = context.portal_workflow
+portal_workflow.doActionFor(context, action='edit_action', comment='Visited by WechatEvent_processUpdate') """ )
+    self.commit()
+
+  def _dropWechatEvent_processUpdate(self):
+    script_name = 'WechatEvent_processUpdate'
+    if script_name in self.portal.portal_skins.custom.objectIds():
+      self.portal.portal_skins.custom.manage_delObjects(script_name)
+    self.commit()
+
+  def test_updateStatus_defaultUseCase(self):
+    event = self.createWechatEvent()
+    payment = self.createPaymentTransaction()
+    event.edit(
+      destination_value=payment,
+      source="portal_secure_payments/slapos_wechat_test",
+    )
+    transaction_date, transaction_id = \
+      payment.PaymentTransaction_generateWechatId()
+
+    mocked_data_kw = 'mocked_data_kw'
+    mocked_signature = 'mocked_signature'
+    mocked_sent_text = 'mocked_sent_text'
+    mocked_received_text = 'mocked_received_text'
+
+    self._simulateWechatEvent_processUpdate()
+    try:
+      self.mockSoapGetInfo(
+        event.updateStatus,
+        (transaction_date.toZone('UTC').asdatetime(), transaction_id),
+        (mocked_data_kw, mocked_signature, mocked_sent_text, mocked_received_text),
+      )
+    finally:
+      self._dropWechatEvent_processUpdate()
+
+    event_message_list = event.contentValues(portal_type="Wechat Event Message")
+    self.assertEqual(len(event_message_list), 2)
+
+    sent_message = [x for x in event_message_list \
+                    if x.getTitle() == 'Sent SOAP'][0]
+    self.assertEqual(sent_message.getTextContent(), mocked_sent_text)
+
+    received_message = [x for x in event_message_list \
+                        if x.getTitle() == 'Received SOAP'][0]
+    self.assertEqual(received_message.getPredecessor(), 
+                      sent_message.getRelativeUrl())
+    self.assertEqual(received_message.getTextContent(), mocked_received_text)
+
+    self.assertEqual(
+        'Visited by WechatEvent_processUpdate',
+        event.workflow_history['edit_workflow'][-1]['comment'])
+
diff --git a/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.xml b/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c6c58aa18bc232d7fcc7d67a9c9583b3a811b026
--- /dev/null
+++ b/master/bt5/slapos_wechat/TestTemplateItem/portal_components/test.erp5.testSlapOSWechatWorkflow.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Test Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_recorded_property_dict</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>testSlapOSWechatWorkflow</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>test.erp5.testSlapOSWechatWorkflow</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Test Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary/>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="4" aka="AAAAAAAAAAQ=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
+    </pickle>
+    <pickle>
+      <tuple>
+        <none/>
+        <list>
+          <dictionary>
+            <item>
+                <key> <string>action</string> </key>
+                <value> <string>validate</string> </value>
+            </item>
+            <item>
+                <key> <string>validation_state</string> </key>
+                <value> <string>validated</string> </value>
+            </item>
+          </dictionary>
+        </list>
+        <none/>
+      </tuple>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/master/bt5/slapos_wechat/bt/template_test_id_list b/master/bt5/slapos_wechat/bt/template_test_id_list
index 8c2cd79ba95d74dd8609e436c79cfab12b50e33c..f0cb8f91c7ef1f0378a0b75f229383ba7fa2b5b1 100644
--- a/master/bt5/slapos_wechat/bt/template_test_id_list
+++ b/master/bt5/slapos_wechat/bt/template_test_id_list
@@ -1 +1,2 @@
-test.erp5.testSlapOSWechatSkins
\ No newline at end of file
+test.erp5.testSlapOSWechatSkins
+test.erp5.testSlapOSWechatWorkflow
\ No newline at end of file