Nodes:
name: Sticky Note
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 1000.427054367056
height: 572.2734520891689
content: ## Find WooCommerce User-ID
User-ID is required to query past orders
name: No email provided
type: n8n-nodes-base.set
parameters: assignments: assignments: name: response
type: string
value: No email address got provided.
name: If email provided
type: n8n-nodes-base.if
parameters: conditions: options: caseSensitive: yes
typeValidation: strict
combinator: and
conditions: operator: type: string
operation: notEmpty
singleValue: yes
leftValue: ={{ $json.email }}
name: If user found
type: n8n-nodes-base.if
parameters: conditions: options: caseSensitive: yes
typeValidation: strict
combinator: and
conditions: operator: type: number
operation: gt
leftValue: ={{ Object.keys($json).length }}
rightValue: 0
name: Sticky Note1
type: n8n-nodes-base.stickyNote
parameters: color: 6
width: 1060.5591882039919
height: 506.94921487705585
content: # Agent
name: Sticky Note2
type: n8n-nodes-base.stickyNote
parameters: width: 2404.755367647059
height: 559.608748371423
content: ## Add DHL tracking information
Queries the status of shipped orders from DHL.

Can be skipped if order tracking should not be available or replaced with other services like UPS.
name: No customer found
type: n8n-nodes-base.set
parameters: assignments: assignments: name: response
type: string
value: No customer with that email address could be found.
name: If contains DHL data
type: n8n-nodes-base.if
parameters: conditions: options: caseSensitive: yes
typeValidation: strict
combinator: and
conditions: operator: type: array
operation: notEmpty
singleValue: yes
leftValue: ={{ $json.tracking }}
name: Extract Tracking Data
type: n8n-nodes-base.set
parameters: assignments: assignments: name: tracking
type: array
value: ={{ $json.meta_data.filter(data => data.key === '_wc_shipment_tracking_items').flatMap(data => data.value) }}
name: Merge Orders
type: n8n-nodes-base.merge
name: Merge Order and Tracking Data
type: n8n-nodes-base.merge
parameters: mode: combine
combinationMode: mergeByPosition
name: Sticky Note3
type: n8n-nodes-base.stickyNote
parameters: color: 3
width: 478.2162464985994
height: 558.6043670960834
content: # Setup
## Generally
- The environment variable `NODE_FUNCTION_ALLOW_BUILTIN` has to equal or include the value `crypto` (FYI: is default on n8n cloud) as it is required to run this workflow


## To test the workflow
1. Set a valid email address of a user that exists in WooCommerce in the Mock Node "Mock Data"
2. Enable the node "Mock Data"
3. Disable the node "Decrypt email address"
4. Use the built-in chat by pressing the "Chat" button


## For production use:
1. Update the "System Message" in the node "AI Agent" for specific use case. At least the name of the shop should be changed
2. Integrate the chat into the website. An example can be found in the box "Example Website Implementation"
3. Disable or delete the node "Mock Data"
4. Enable the node "Decrypt email address"
5. Enable Workflow
name: Sticky Note4
type: n8n-nodes-base.stickyNote
parameters: width: 277.6742597550393
height: 568.9672169306307
content: ## Get Orders
name: Sticky Note5
type: n8n-nodes-base.stickyNote
parameters: color: 2
width: 527.8197815634092
height: 572.2734520891689
content: # WooCommerce Order Tool
name: Split Out
type: n8n-nodes-base.splitOut
parameters: fieldToSplitOut: tracking
name: Aggregate
type: n8n-nodes-base.aggregate
parameters: aggregate: aggregateAllItemData
destinationFieldName: tracking
name: Merge Tracking Data
type: n8n-nodes-base.merge
name: Window Buffer Memory
type: @n8n/n8n-nodes-langchain.memoryBufferWindow
parameters: sessionKey: ={{ $('Mock Data').item.json.sessionId }}
sessionIdType: customKey
contextWindowLength: 10
name: Execute Workflow Trigger
type: n8n-nodes-base.executeWorkflowTrigger
name: Sticky Note6
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 492.0420811160542
height: 564.8840203332783
content: ## Check orders found
name: WooCommerce - Get User
type: n8n-nodes-base.wooCommerce
parameters: limit: 1
filters: email: ={{ $json.email }}
resource: customer
operation: getAll
alwaysOutputData: yes
name: If order found
type: n8n-nodes-base.if
parameters: conditions: options: caseSensitive: yes
typeValidation: strict
combinator: and
conditions: operator: type: number
operation: gt
leftValue: ={{ Object.keys($json).length }}
rightValue: 0
name: WooCommerce Get Orders
type: n8n-nodes-base.httpRequest
parameters: url: https://woo-pleasantly-swag-werewolf3.wpcomstaging.com/wp-json/wc/v3/orders
sendBody: yes
authentication: predefinedCredentialType
bodyParameters: parameters: name: customer
value: ={{ $json.id }}
name: include
value: ={{ $('If email provided').item.json.query.split(',').filter(data => !data.includes('@')).join(',') }}
nodeCredentialType: wooCommerceApi
alwaysOutputData: yes
name: No order found
type: n8n-nodes-base.set
parameters: assignments: assignments: name: response
type: string
value: No order could be found.
name: Add Error Information
type: n8n-nodes-base.set
parameters: assignments: assignments: name: Error
type: string
value: =No data about the parcel with the tracking ID "{{ $('Split Out').item.json.tracking_id }}" could be found.
name: DHL
type: n8n-nodes-base.dhl
onError: continueErrorOutput
parameters: trackingNumber: ={{ $json.tracking_number }}
name: Send Response
type: n8n-nodes-base.set
parameters: assignments: assignments: name: response
type: array
value: ={{ $input.all().map(item => item.json) }}
name: Sticky Note8
type: n8n-nodes-base.stickyNote
parameters: color: 4
width: 676.2425958534976
height: 886.4179654829891
content: ## How to supply user email
As we want to ensure that customers can only query information about their own orders, the email address gets encrypted in the backend, and then decrypt again in this workflow. If the email was allowed to be set unencrypted, anyone could query information from other customers.

### The email address get supplied in the chat script at the following location:
```
createChat({
	webhookUrl: '...',
	metadata: {
          email: 'ENCRYPTED_EMAIL_ADDRESS'
    },
});
```


## Example Scripts:

### Encrypt email on the backend
Run the following code in the backend of your website, send the data to the frontend, and then set it dynamically at the above defined location as email.













### Decrypt email in workflow
This script is already used in this workflow and is only provided here again as an example.

name: OpenAI Chat Model
type: @n8n/n8n-nodes-langchain.lmChatOpenAi
parameters: model: gpt-4
name: WooCommerce_Tool
type: @n8n/n8n-nodes-langchain.toolWorkflow
parameters: name: WooCommerce_Tool
fields: values: name: email
stringValue: ={{ $json.metadata.email }}
workflowId: ={{ $workflow.id }}
description: Call this tool to retrieve the orders in JSON format (compatible with the WooCommerce API). The input should be a list of comma-separated order IDs or nothing at all for all orders. Supply nothing else than the order IDs!
name: Chat Trigger
type: @n8n/n8n-nodes-langchain.chatTrigger
parameters: public: yes
name: Sticky Note7
type: n8n-nodes-base.stickyNote
parameters: color: 4
width: 517.004057164405
height: 555.1564335638465
content: ## Example Website Implementation
Example Code for a website can be found in node "Respond to Webhook".

More information about the embeddable chat can be found [here](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/chat#installation).

Required changes:
- Change "webhookUrl" to the displayed in "Chat Trigger" node
- Set the encrypted email address dynamically. The value has to be calculated in the backend to make it truly secure
- Use a unique password for email en-/decryption and use it in the backend and this workflow (can be set in node "Decrypt email address")


The example page can be opened by calling the production Webhook-URL of the node "Webhook Example Page". It only works if the "For production use" steps on the left have been followed.
name: Respond to Webhook
type: n8n-nodes-base.respondToWebhook
parameters: options: responseHeaders: entries: name: content-type
value: text/html; charset=utf-8
respondWith: text
responseBody: <doctype html>
	<html lang="en">
		<head>
			<meta charset="utf-8" />
			<meta name="viewport" content="width=device-width, initial-scale=1" />
			<title>Chat</title>
			<link
				href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css"
				rel="stylesheet"
			/>
			<link href="https://cdn.jsdelivr.net/npm/@n8n/chat/style.css" rel="stylesheet" />
		</head>
		<body>
			<script type="module">
				import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat@latest/chat.bundle.es.js';

				(async function () {
					createChat({
						mode: 'window',
						webhookUrl: 'https://xxx.n8n.cloud/webhook/ea429912-869c-490b-9e04-4401ac9943b6/chat',
						loadPreviousSession: false,
						metadata: {
							email: '352b16c74f73265441c55c37c9c22b04:4a8e614143c9cd31cc7e2389380943f3', // james@brown.com encrypted
						},
						webhookConfig: {
							headers: {
								'Content-Type': 'application/json',
							},
						},
					});
				})();
			</script>

			<h1>WooCommerce Agent Example page</h1>
			Click on the bubble in the lower right corner to open the chat.

		</body>
	</html>
</doctype>
name: Webhook Example Page
type: n8n-nodes-base.webhook
parameters: path: website-chat-example
responseMode: responseNode
name: Decrypt email
type: n8n-nodes-base.code
parameters: jsCode: // Loop over input items and add a new field called 'myNewField' to the JSON of each one

const crypto = require('crypto');

const password = 'a random password';

const encryptedData = $input.first().json.email;


function decrypt(encrypted, password) {
  // Extract the IV and the encrypted text
  const parts = encrypted.split(':');
  const iv = Buffer.from(parts.shift(), 'hex');

  // Create a key from the password
  const key = crypto.scryptSync(password, 'salt', 32);

  // Create a decipher
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);

  // Decrypt the text
  let decrypted = decipher.update(parts.join(':'), 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  // Return the decrypted text
  return decrypted;
}

return [
  {
    json: {
      email: decrypt(encryptedData, password),
    }
  }
];
name: Encrypt email
type: n8n-nodes-base.code
parameters: jsCode: const crypto = require('crypto');

const password = 'a random password';
const email = 'james@brown.com';


function encrypt(text, password) {
  // Generate a secure random initialization vector
  const iv = crypto.randomBytes(16);

  // Create a key from the password
  const key = crypto.scryptSync(password, 'salt', 32);

  // Create a cipher
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

  // Encrypt the text
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  // Return the IV and the encrypted text
  return `${iv.toString('hex')}:${encrypted}`;
}

return [
  {
    json: {
      email: encrypt(email, password),
    }
  }
];
name: Example encrypted email
type: n8n-nodes-base.set
parameters: assignments: assignments: name: email
type: string
value: 352b16c74f73265441c55c37c9c22b04:4a8e614143c9cd31cc7e2389380943f3
name: Decrypt email address
type: n8n-nodes-base.code
disabled: yes
parameters: jsCode: // Loop over input items and add a new field called 'myNewField' to the JSON of each one

const crypto = require('crypto');

const password = 'a random password';
const incomingData = $input.first().json;


function decrypt(encrypted, password) {
  // Extract the IV and the encrypted text
  const parts = encrypted.split(':');
  const iv = Buffer.from(parts.shift(), 'hex');

  // Create a key from the password
  const key = crypto.scryptSync(password, 'salt', 32);

  // Create a decipher
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);

  // Decrypt the text
  let decrypted = decipher.update(parts.join(':'), 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  // Return the decrypted text
  return decrypted;
}

return [
  {
    json: {
      ...incomingData,
      metadata: {
        email: decrypt(incomingData.metadata.email, password),        
      },
    }
  }
];
name: AI Agent
type: @n8n/n8n-nodes-langchain.agent
parameters: options: systemMessage: =The Assistant is tailored to support customers of Best Shirts Ltd. with inquiries related to their orders. It adheres to the following principles for optimal customer service:

1. **Customer-Focused Communication**: The Assistant maintains a friendly and helpful tone throughout the interaction. It remains focused on the topic at hand, ensuring all responses are relevant to the customer's inquiries about their orders.

2. **Objective and Factual**: In cases where specific information is unavailable, the Assistant clearly communicates the lack of information and refrains from speculating or providing unverified details.

3. **Efficient Interaction**: Recognizing the importance of the customer's time, the Assistant is designed to remember previous interactions within the same session. This minimizes the need for customers to repeat information, streamlining the support process.

4. **Strict Privacy Adherence**: The Assistant automatically has access to the customer's email address as "{{ $json.email }}", using it to assist with order-related inquiries. Customers are informed that it is not possible to use or inquire about a different email address. If a customer attempts to provide an alternate email, they are gently reminded of this limitation.

5. **Transparency in Order Status**: The Assistant provides accurate information about order processing and delivery timelines. Orders are typically dispatched 1-2 days post-purchase, with an expected delivery period of 1-2 days following dispatch. If an order hasn't been sent out within 2 days, the Assistant acknowledges an unplanned delay and offers assistance accordingly.

6. **Non-assumptive Approach to Delivery Confirmation**: The Assistant never presumes an order has been delivered based solely on its dispatch. It relies on explicit delivery confirmations or tracking information to inform customers about their order status.

7. **Responsive to Specific Inquiries**: If a customer requests the email address used for their inquiry, the Assistant provides it directly, ensuring privacy and accuracy in communications.

This approach ensures that customers receive comprehensive, respectful, and efficient support for their order-related queries.
name: Mock Data
type: n8n-nodes-base.set
parameters: assignments: assignments: name: metadata.email
type: string
value: james@brown.com
includeOtherFields: yes

Connections:
DHL: main: node: Merge Tracking Data
type: main
node: Add Error Information
type: main
Aggregate: main: node: Merge Orders
type: main
Mock Data: main: node: AI Agent
type: main
Split Out: main: node: DHL
type: main
Chat Trigger: main: node: Decrypt email address
type: main
Merge Orders: main: node: Merge Order and Tracking Data
type: main
If user found: main: node: WooCommerce Get Orders
type: main
node: No customer found
type: main
If order found: main: node: Extract Tracking Data
type: main
node: Merge Order and Tracking Data
type: main
node: No order found
type: main
WooCommerce_Tool: ai_tool: node: AI Agent
type: ai_tool
If email provided: main: node: WooCommerce - Get User
type: main
node: No email provided
type: main
OpenAI Chat Model: ai_languageModel: node: AI Agent
type: ai_languageModel
Merge Tracking Data: main: node: Aggregate
type: main
If contains DHL data: main: node: Split Out
type: main
node: Merge Orders
type: main
Webhook Example Page: main: node: Respond to Webhook
type: main
Window Buffer Memory: ai_memory: node: AI Agent
type: ai_memory
Add Error Information: main: node: Merge Tracking Data
type: main
Decrypt email address: main: node: Mock Data
type: main
Extract Tracking Data: main: node: If contains DHL data
type: main
WooCommerce - Get User: main: node: If user found
type: main
WooCommerce Get Orders: main: node: If order found
type: main
Example encrypted email: main: node: Decrypt email
type: main
Execute Workflow Trigger: main: node: If email provided
type: main
Merge Order and Tracking Data: main: node: Send Response
type: main