Cash on Delivery workflow action
Author:
Valery Kornilovich
Changed on:
8 July 2024
Key Points
- Understanding the Market: Recognizing the importance of Cash on Delivery (CoD) in markets like India, where it is a preferred payment method.
- Global Adaptability: The workflow's design serves a global audience, providing a template that can be customised for regions where upfront payment is not the norm.
- Seamless Integration: Ensuring the CoD process is smoothly incorporated into the existing order journey to enhance customer experience.
- Operational Efficiency: Streamlining the CoD transaction to ensure quick and secure payment collection at the point of delivery.
Prerequisites
Steps
Use Case
As a Consumer, I want to pay for my order items upon collection or delivery arrival.
Solution Approach
- Place a CC or HD order over API containing the following order attribute:
1"attributes": [
2 {
3 "name": "CASH_ON_DELIVERY",
4 "type": "STRING",
5 "value": "TRUE"
6 }
7]
Language: json
Name: Order Attribute
Description:
Attribute to activate 'Cash on Delivery' functionality
- Process the order through the normal fulfillment workflow.
- As a final step before the orders are complete, there is a holding state where user actions are presented in the Fluent OMS app. This will enable a user to manually simulate that a payment event is received into Fluent from an external source.
Challenges / Limitiations
The main limitation here is that we can’t receive an order from a demo commerce platform such as Salesforce or Adobe as this would require an extension of these connectors. Therefore this demo scenario must be performed through API creation of orders via Postman.
Workflow changes
Add a series of workflow changes to include a new status, user action and underpinning ruleset to enable this step at the end of the CC and HD order entity workflows.
1 {
2 "name": "CheckIfOrderIsComplete",
3 "description": "Checks if all fulfilments are in any of the configured states and sends an event",
4 "type": "ORDER",
5 "eventType": "NORMAL",
6 "rules": [
7 {
8 "name": "{{fluent.account.id}}.order.SendEventOnVerifyingAllFulfilmentStates",
9 "props": {
10 "status": [
11 "COMPLETE",
12 "REJECTED",
13 "EXPIRED",
14 "ESCALATED"
15 ],
16 "eventName": "CheckEnablementCashOnDeliveryLogic"
17 }
18 }
19 ],
20 "triggers": [
21 {
22 "status": "AWAITING_CUSTOMER_COLLECTION"
23 },
24 {
25 "status": "BOOKED"
26 },
27 {
28 "status": "PICK_PACK"
29 }
30 ],
31 "userActions": []
32 },
33
Language: json
Name: Entry point of 'Cash on Delivery' functionality
Description:
Replace OrderComplete with CheckEnablementCashOnDeliveryLogic
1 {
2 "name": "ContinueWithoutTransaction",
3 "description": "Continue of Order processing without creating of a new transaction",
4 "type": "ORDER",
5 "subtype": "CC",
6 "eventType": "NORMAL",
7 "rules": [
8 {
9 "name": "{{fluent.account.id}}.core.SendEvent",
10 "props": {
11 "eventName": "OrderComplete"
12 }
13 }
14 ],
15 "triggers": [
16 {
17 "status": "AWAITING_TRANSACTION"
18 }
19 ],
20 "userActions": [
21 {
22 "context": [
23 {
24 "label": "Use Credit Card",
25 "type": "PRIMARY",
26 "modules": [
27 "adminconsole",
28 "servicepoint"
29 ],
30 "confirm": true
31 }
32 ],
33 "attributes": []
34 }
35 ]
36 },
37 {
38 "name": "ContinueWithTransaction",
39 "description": "Continue of Order processing with creating of a new transaction (with PaymentType CASH by default)",
40 "type": "ORDER",
41 "subtype": "CC",
42 "eventType": "NORMAL",
43 "rules": [
44 {
45 "name": "FLUENTRETAIL.base.CreateTransactionForOrder",
46 "props": {}
47 },
48 {
49 "name": "{{fluent.account.id}}.core.ScheduleEvent",
50 "props": {
51 "delay": 1,
52 "eventName": "CheckTransaction"
53 }
54 }
55 ],
56 "triggers": [
57 {
58 "status": "AWAITING_TRANSACTION"
59 }
60 ],
61 "userActions": [
62 {
63 "context": [
64 {
65 "label": "Cash On Delivery",
66 "type": "PRIMARY",
67 "modules": [
68 "adminconsole",
69 "servicepoint"
70 ],
71 "confirm": false
72 }
73 ],
74 "attributes": [
75 {
76 "name": "amount",
77 "label": "Amount",
78 "type": "STRING",
79 "source": "",
80 "defaultValue": "100",
81 "mandatory": false
82 },
83 {
84 "name": "cardType",
85 "label": "Card Type",
86 "type": "STRING",
87 "source": "",
88 "defaultValue": "INTERAC",
89 "mandatory": false
90 },
91 {
92 "name": "currency",
93 "label": "Currency",
94 "type": "STRING",
95 "source": "",
96 "defaultValue": "USD",
97 "mandatory": false
98 },
99 {
100 "name": "paymentMethod",
101 "label": "Payment Method",
102 "type": "STRING",
103 "source": "",
104 "defaultValue": "CASH",
105 "mandatory": true
106 },
107 {
108 "name": "paymentProvider",
109 "label": "Payment Provider",
110 "type": "STRING",
111 "source": "",
112 "defaultValue": "AFTERPAY",
113 "mandatory": false
114 },
115 {
116 "name": "transactionCode",
117 "label": "Transaction Code",
118 "type": "STRING",
119 "source": "",
120 "defaultValue": "TRC-1234",
121 "mandatory": false
122 },
123 {
124 "name": "transactionRef",
125 "label": "Transaction Ref",
126 "type": "STRING",
127 "source": "",
128 "defaultValue": "TR-1234",
129 "mandatory": false
130 },
131 {
132 "name": "transactionType",
133 "label": "Transaction Type",
134 "type": "STRING",
135 "source": "",
136 "defaultValue": "PAYMENT",
137 "mandatory": false
138 }
139 ]
140 }
141 ]
142 },
143 {
144 "name": "CheckEnablementCashOnDeliveryLogic",
145 "description": "Checking that Order has CASH_ON_DELIVERY attribute",
146 "type": "ORDER",
147 "eventType": "NORMAL",
148 "rules": [
149 {
150 "name": "{{fluent.account.id}}.order.SendEventOnVerifyingAttributeValue",
151 "props": {
152 "eventName": "AwaitingTransaction",
153 "noMatchEventName": "OrderComplete",
154 "attributeName": "CASH_ON_DELIVERY",
155 "attributeValue": "TRUE"
156 }
157 }
158 ],
159 "triggers": [
160 {
161 "status": "BOOKED"
162 },
163 {
164 "status": "PICK_PACK"
165 },
166 {
167 "status": "COMPLETE"
168 },
169 {
170 "status": "AWAITING_CUSTOMER_COLLECTION"
171 }
172 ],
173 "userActions": []
174 },
175 {
176 "name": "CheckPayment",
177 "description": "Checking that Order has CASH transaction (according to attribute IS_CASH_ON_DELIVERY)",
178 "type": "ORDER",
179 "eventType": "NORMAL",
180 "rules": [
181 {
182 "name": "{{fluent.account.id}}.order.SendEventOnVerifyingAttributeValue",
183 "props": {
184 "eventName": "AwaitingPayment",
185 "noMatchEventName": "OrderComplete",
186 "attributeName": "TRANSACTION_CASH_EXISTS",
187 "attributeValue": "TRUE"
188 }
189 }
190 ],
191 "triggers": [
192 {
193 "status": "AWAITING_CUSTOMER_COLLECTION"
194 },
195 {
196 "status": "BOOKED"
197 },
198 {
199 "status": "PICK_PACK"
200 },
201 {
202 "status": "AWAITING_TRANSACTION"
203 }
204 ],
205 "userActions": []
206 },
207 {
208 "name": "AwaitingPayment",
209 "description": "Changing state of Order to AWAITING_PAYMENT for orders with CASH transaction",
210 "type": "ORDER",
211 "eventType": "NORMAL",
212 "rules": [
213 {
214 "name": "{{fluent.account.id}}.core.SetState",
215 "props": {
216 "status": "AWAITING_PAYMENT"
217 }
218 }
219 ],
220 "triggers": [
221 {
222 "status": "AWAITING_CUSTOMER_COLLECTION"
223 },
224 {
225 "status": "PICK_PACK"
226 },
227 {
228 "status": "BOOKED"
229 },
230 {
231 "status": "AWAITING_TRANSACTION"
232 }
233 ],
234 "userActions": []
235 },
236 {
237 "name": "ConfirmPayment",
238 "description": "Confirmation that payment collected",
239 "type": "ORDER",
240 "subtype": "CC",
241 "eventType": "NORMAL",
242 "rules": [
243 {
244 "name": "{{fluent.account.id}}.core.SendEvent",
245 "props": {
246 "eventName": "OrderComplete"
247 }
248 }
249 ],
250 "triggers": [
251 {
252 "status": "AWAITING_PAYMENT"
253 }
254 ],
255 "userActions": [
256 {
257 "context": [
258 {
259 "label": "Paid",
260 "type": "PRIMARY",
261 "modules": [
262 "servicepoint",
263 "adminconsole"
264 ],
265 "confirm": true
266 }
267 ],
268 "attributes": []
269 }
270 ]
271 },
272 {
273 "name": "AwaitingTransaction",
274 "description": "Change status of the Order to AWAITING_TRANSACTION",
275 "type": "ORDER",
276 "eventType": "NORMAL",
277 "rules": [
278 {
279 "name": "{{fluent.account.id}}.core.SetState",
280 "props": {
281 "status": "AWAITING_TRANSACTION"
282 }
283 }
284 ],
285 "triggers": [
286 {
287 "status": "BOOKED"
288 },
289 {
290 "status": "PICK_PACK"
291 },
292 {
293 "status": "COMPLETE"
294 },
295 {
296 "status": "AWAITING_CUSTOMER_COLLECTION"
297 }
298 ],
299 "userActions": []
300 },
301 {
302 "name": "CheckTransaction",
303 "description": "Check that the Order has transaction with type CASH",
304 "type": "ORDER",
305 "eventType": "NORMAL",
306 "rules": [
307 {
308 "name": "{{fluent.account.id}}.{packageName}.MarkOrderAccordingToTransactionPaymentMethod",
309 "props": {
310 "paymentMethod": "CASH",
311 "attributeName": "TRANSACTION_CASH_EXISTS"
312 }
313 },
314 {
315 "name": "{{fluent.account.id}}.core.SendEvent",
316 "props": {
317 "eventName": "CheckPayment"
318 }
319 }
320 ],
321 "triggers": [
322 {
323 "status": "AWAITING_TRANSACTION"
324 }
325 ],
326 "userActions": []
327 }
328
Language: json
Name: Example of changes in Workflow
Description:
Example of changes in Workflow
Custom Rule
The new rule MarkOrderAccordingToTransactionPaymentMethod has been added to a custom rule plugin. This rule checks if a Financial Transaction exists for Order and Payment Method equal to the specified method then adds a specified attribute to Order to mark the entity.
Property | Value |
Plugin name | <yourPluginName> |
Rule API Client | GraphQL |
Rule Info Description | Mark the Order as {
|
Supported Entities | ORDER |
1@RuleInfo(name = "MarkOrderAccordingToTransactionPaymentMethod",
2 description = "Mark the Order as {attributeName} "
3 + "when the Order has transaction with {paymentMethod} Payment Method",
4 accepts = {
5 @EventInfo(entityType = "ORDER")
6 })
7@ParamString(name = "attributeName",
8 description = "Mark attribute name")
9@ParamString(name = "paymentMethod",
10 description = "Payment Method for checking")
11public class MarkOrderAccordingToTransactionPaymentMethod implements Rule {
12
13 @Override
14 public <C extends Context> void run(C context) {
15 String markAttributeName = context.getProp("attributeName", String.class);
16 if (StringUtils.isBlank(markAttributeName)) {
17 throw new PropertyNotFoundException(400, "attributeName is blank");
18 }
19
20 String paymentMethod = context.getProp("paymentMethod", String.class);
21 if (StringUtils.isBlank(paymentMethod)) {
22 throw new PropertyNotFoundException(400, "paymentMethod is blank");
23 }
24
25 String orderId = context.getEvent().getEntityId();
26 if (StringUtils.isBlank(orderId)) {
27 throw new RuleExecutionException("order id is blank", context.getEvent());
28 }
29
30 Data orderById =
31 OrderUtils.getOrderByIdQuery(context, orderId, false, false, false, false, false, false, false, true);
32
33 if (orderById == null || orderById.order() == null) {
34 throw NoOrderFoundException.id(orderId, context.getEvent());
35 }
36
37 if (orderById.order().financialTransactions() == null
38 || orderById.order().financialTransactions().edges() == null) {
39 return;
40 }
41
42 boolean attributeDetected = orderById.order().financialTransactions().edges().stream().anyMatch(edge ->
43 edge != null && edge.node() != null && paymentMethod.equals(edge.node().paymentMethod())
44 );
45
46 if (!attributeDetected) {
47 return;
48 }
49
50 List<AttributeInput> attributeInputList =
51 Collections.singletonList(AttributeUtils.inputOf(markAttributeName, true));
52 new OrderService(context).updateOrderAttributes(attributeInputList);
53 }
54}
55
Language: java
Name: Example of MarkOrderAccordingToTransactionPaymentMethod
Description:
MarkOrderAccordingToTransactionPaymentMethod rule
Result
As a result, we will have User Actions 'Cash on Delivery' and 'Use Credit Card' to finalize the Order.
