@chanaka_nakandala/integration-agent 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +369 -60
- package/dist/index.js +22 -486
- package/dist/index.js.map +1 -1
- package/dist/{cli/init-command.d.ts → init-command.d.ts} +2 -2
- package/dist/init-command.d.ts.map +1 -0
- package/dist/{cli/init-command.js → init-command.js} +72 -46
- package/dist/init-command.js.map +1 -0
- package/package.json +5 -11
- package/agent-personas/ba-agent.yaml +0 -682
- package/agent-personas/developer-agent.yaml +0 -585
- package/dist/cache/file-cache.d.ts +0 -10
- package/dist/cache/file-cache.d.ts.map +0 -1
- package/dist/cache/file-cache.js +0 -79
- package/dist/cache/file-cache.js.map +0 -1
- package/dist/cli/init-command.d.ts.map +0 -1
- package/dist/cli/init-command.js.map +0 -1
- package/dist/scrapers/docs-scraper.d.ts +0 -28
- package/dist/scrapers/docs-scraper.d.ts.map +0 -1
- package/dist/scrapers/docs-scraper.js +0 -200
- package/dist/scrapers/docs-scraper.js.map +0 -1
- package/dist/search/keyword-search.d.ts +0 -39
- package/dist/search/keyword-search.d.ts.map +0 -1
- package/dist/search/keyword-search.js +0 -127
- package/dist/search/keyword-search.js.map +0 -1
- package/dist/tools/code-generation-tool.d.ts +0 -33
- package/dist/tools/code-generation-tool.d.ts.map +0 -1
- package/dist/tools/code-generation-tool.js +0 -62
- package/dist/tools/code-generation-tool.js.map +0 -1
- package/dist/tools/search-result-formatter.d.ts +0 -27
- package/dist/tools/search-result-formatter.d.ts.map +0 -1
- package/dist/tools/search-result-formatter.js +0 -89
- package/dist/tools/search-result-formatter.js.map +0 -1
- package/dist/tools/search-tool.d.ts +0 -9
- package/dist/tools/search-tool.d.ts.map +0 -1
- package/dist/tools/search-tool.js +0 -32
- package/dist/tools/search-tool.js.map +0 -1
- package/dist/tools/template-loader.d.ts +0 -54
- package/dist/tools/template-loader.d.ts.map +0 -1
- package/dist/tools/template-loader.js +0 -148
- package/dist/tools/template-loader.js.map +0 -1
- package/dist/types/search.d.ts +0 -21
- package/dist/types/search.d.ts.map +0 -1
- package/dist/types/search.js +0 -2
- package/dist/types/search.js.map +0 -1
- package/templates/README.md +0 -98
- package/templates/authenticate/curl.template +0 -97
- package/templates/authenticate/java.template +0 -155
- package/templates/authenticate/python.template +0 -111
- package/templates/authenticate/typescript.template +0 -98
- package/templates/create_menu/curl.template +0 -145
- package/templates/create_menu/java.template +0 -285
- package/templates/create_menu/python.template +0 -159
- package/templates/create_menu/typescript.template +0 -184
- package/templates/receive_order/curl.template +0 -138
- package/templates/receive_order/java.template +0 -263
- package/templates/receive_order/python.template +0 -157
- package/templates/receive_order/typescript.template +0 -194
- package/templates/update_item_availability/curl.template +0 -143
- package/templates/update_item_availability/java.template +0 -279
- package/templates/update_item_availability/python.template +0 -203
- package/templates/update_item_availability/typescript.template +0 -194
- package/templates/update_order_status/curl.template +0 -138
- package/templates/update_order_status/java.template +0 -202
- package/templates/update_order_status/python.template +0 -142
- package/templates/update_order_status/typescript.template +0 -139
@@ -1,263 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Grubtech Order Receive Webhook - Java (Spring Boot)
|
3
|
-
*
|
4
|
-
* This example demonstrates how to receive orders from Grubtech
|
5
|
-
* via webhook and process them in your system.
|
6
|
-
*
|
7
|
-
* Prerequisites:
|
8
|
-
* - Java 17+
|
9
|
-
* - Spring Boot 3.x
|
10
|
-
*
|
11
|
-
* Replace the following placeholders:
|
12
|
-
* - {{WEBHOOK_SECRET}}: Your webhook secret for signature verification
|
13
|
-
* - {{ORDER_PROCESSING_LOGIC}}: Your business logic to process the order
|
14
|
-
*/
|
15
|
-
|
16
|
-
package com.example.grubtech;
|
17
|
-
|
18
|
-
import org.springframework.boot.SpringApplication;
|
19
|
-
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
20
|
-
import org.springframework.http.HttpStatus;
|
21
|
-
import org.springframework.http.ResponseEntity;
|
22
|
-
import org.springframework.web.bind.annotation.*;
|
23
|
-
|
24
|
-
import javax.crypto.Mac;
|
25
|
-
import javax.crypto.spec.SecretKeySpec;
|
26
|
-
import java.nio.charset.StandardCharsets;
|
27
|
-
import java.security.InvalidKeyException;
|
28
|
-
import java.security.MessageDigest;
|
29
|
-
import java.security.NoSuchAlgorithmException;
|
30
|
-
import java.time.Instant;
|
31
|
-
import java.time.temporal.ChronoUnit;
|
32
|
-
import java.util.List;
|
33
|
-
import java.util.Map;
|
34
|
-
|
35
|
-
@SpringBootApplication
|
36
|
-
@RestController
|
37
|
-
@RequestMapping("/webhooks/grubtech")
|
38
|
-
public class GrubtechOrderWebhook {
|
39
|
-
|
40
|
-
private static final String WEBHOOK_SECRET = "{{WEBHOOK_SECRET}}";
|
41
|
-
|
42
|
-
/**
|
43
|
-
* Verify webhook signature using HMAC-SHA256
|
44
|
-
*/
|
45
|
-
private boolean verifySignature(String body, String signature) {
|
46
|
-
if (signature == null || signature.isEmpty()) {
|
47
|
-
System.err.println("❌ No signature provided");
|
48
|
-
return false;
|
49
|
-
}
|
50
|
-
|
51
|
-
try {
|
52
|
-
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
|
53
|
-
SecretKeySpec secretKey = new SecretKeySpec(
|
54
|
-
WEBHOOK_SECRET.getBytes(StandardCharsets.UTF_8),
|
55
|
-
"HmacSHA256"
|
56
|
-
);
|
57
|
-
sha256Hmac.init(secretKey);
|
58
|
-
|
59
|
-
byte[] hash = sha256Hmac.doFinal(body.getBytes(StandardCharsets.UTF_8));
|
60
|
-
String expectedSignature = bytesToHex(hash);
|
61
|
-
|
62
|
-
// Constant-time comparison
|
63
|
-
boolean isValid = MessageDigest.isEqual(
|
64
|
-
signature.getBytes(StandardCharsets.UTF_8),
|
65
|
-
expectedSignature.getBytes(StandardCharsets.UTF_8)
|
66
|
-
);
|
67
|
-
|
68
|
-
if (!isValid) {
|
69
|
-
System.err.println("❌ Invalid signature");
|
70
|
-
}
|
71
|
-
|
72
|
-
return isValid;
|
73
|
-
|
74
|
-
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
75
|
-
System.err.println("❌ Signature verification error: " + e.getMessage());
|
76
|
-
return false;
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
/**
|
81
|
-
* Convert byte array to hex string
|
82
|
-
*/
|
83
|
-
private String bytesToHex(byte[] bytes) {
|
84
|
-
StringBuilder result = new StringBuilder();
|
85
|
-
for (byte b : bytes) {
|
86
|
-
result.append(String.format("%02x", b));
|
87
|
-
}
|
88
|
-
return result.toString();
|
89
|
-
}
|
90
|
-
|
91
|
-
/**
|
92
|
-
* Process order (replace with your business logic)
|
93
|
-
*/
|
94
|
-
private WebhookResponse processOrder(Order order) {
|
95
|
-
try {
|
96
|
-
System.out.println("📦 Processing order: " + order.getOrderId());
|
97
|
-
System.out.println("Customer: " + order.getCustomer().get("name"));
|
98
|
-
System.out.println("Items: " + order.getItems().size());
|
99
|
-
System.out.println("Total: $" + order.getTotals().get("total"));
|
100
|
-
|
101
|
-
// {{ORDER_PROCESSING_LOGIC}}
|
102
|
-
// Example: Save to database, notify kitchen, etc.
|
103
|
-
|
104
|
-
// Simulate processing
|
105
|
-
Thread.sleep(100);
|
106
|
-
|
107
|
-
// Calculate estimated ready time (30 minutes from now)
|
108
|
-
String readyTime = Instant.now()
|
109
|
-
.plus(30, ChronoUnit.MINUTES)
|
110
|
-
.toString();
|
111
|
-
|
112
|
-
return new WebhookResponse(
|
113
|
-
true,
|
114
|
-
order.getOrderId(),
|
115
|
-
"POS-" + System.currentTimeMillis(),
|
116
|
-
"Order accepted successfully",
|
117
|
-
readyTime
|
118
|
-
);
|
119
|
-
|
120
|
-
} catch (Exception e) {
|
121
|
-
System.err.println("❌ Order processing error: " + e.getMessage());
|
122
|
-
|
123
|
-
return new WebhookResponse(
|
124
|
-
false,
|
125
|
-
order.getOrderId(),
|
126
|
-
null,
|
127
|
-
"Order rejected: " + e.getMessage(),
|
128
|
-
null
|
129
|
-
);
|
130
|
-
}
|
131
|
-
}
|
132
|
-
|
133
|
-
/**
|
134
|
-
* Webhook endpoint to receive orders from Grubtech
|
135
|
-
*/
|
136
|
-
@PostMapping("/orders")
|
137
|
-
public ResponseEntity<WebhookResponse> receiveOrder(
|
138
|
-
@RequestBody String body,
|
139
|
-
@RequestHeader(value = "X-Grubtech-Signature", required = false) String signature
|
140
|
-
) {
|
141
|
-
try {
|
142
|
-
// Verify signature
|
143
|
-
if (!verifySignature(body, signature)) {
|
144
|
-
return ResponseEntity
|
145
|
-
.status(HttpStatus.UNAUTHORIZED)
|
146
|
-
.body(new WebhookResponse(
|
147
|
-
false,
|
148
|
-
null,
|
149
|
-
null,
|
150
|
-
"Invalid signature",
|
151
|
-
null
|
152
|
-
));
|
153
|
-
}
|
154
|
-
|
155
|
-
// Parse order (using Jackson ObjectMapper in real implementation)
|
156
|
-
// For simplicity, this example uses a simplified approach
|
157
|
-
Order order = parseOrder(body);
|
158
|
-
|
159
|
-
// Validate order data
|
160
|
-
if (order.getOrderId() == null || order.getItems() == null || order.getItems().isEmpty()) {
|
161
|
-
return ResponseEntity
|
162
|
-
.status(HttpStatus.BAD_REQUEST)
|
163
|
-
.body(new WebhookResponse(
|
164
|
-
false,
|
165
|
-
null,
|
166
|
-
null,
|
167
|
-
"Invalid order data",
|
168
|
-
null
|
169
|
-
));
|
170
|
-
}
|
171
|
-
|
172
|
-
// Process order
|
173
|
-
WebhookResponse response = processOrder(order);
|
174
|
-
|
175
|
-
// Send response
|
176
|
-
HttpStatus status = response.isAccepted() ? HttpStatus.OK : HttpStatus.BAD_REQUEST;
|
177
|
-
return ResponseEntity.status(status).body(response);
|
178
|
-
|
179
|
-
} catch (Exception e) {
|
180
|
-
System.err.println("❌ Webhook error: " + e.getMessage());
|
181
|
-
return ResponseEntity
|
182
|
-
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
183
|
-
.body(new WebhookResponse(
|
184
|
-
false,
|
185
|
-
null,
|
186
|
-
null,
|
187
|
-
"Internal server error",
|
188
|
-
null
|
189
|
-
));
|
190
|
-
}
|
191
|
-
}
|
192
|
-
|
193
|
-
/**
|
194
|
-
* Parse order from JSON (simplified - use ObjectMapper in production)
|
195
|
-
*/
|
196
|
-
private Order parseOrder(String body) {
|
197
|
-
// In production, use Jackson ObjectMapper:
|
198
|
-
// return objectMapper.readValue(body, Order.class);
|
199
|
-
return new Order(); // Placeholder
|
200
|
-
}
|
201
|
-
|
202
|
-
/**
|
203
|
-
* Health check endpoint
|
204
|
-
*/
|
205
|
-
@GetMapping("/health")
|
206
|
-
public Map<String, String> healthCheck() {
|
207
|
-
return Map.of("status", "ok");
|
208
|
-
}
|
209
|
-
|
210
|
-
// Data models
|
211
|
-
public static class Order {
|
212
|
-
private String orderId;
|
213
|
-
private Map<String, String> customer;
|
214
|
-
private Map<String, Object> delivery;
|
215
|
-
private List<Map<String, Object>> items;
|
216
|
-
private Map<String, Double> totals;
|
217
|
-
private Map<String, String> payment;
|
218
|
-
|
219
|
-
// Getters and setters
|
220
|
-
public String getOrderId() { return orderId; }
|
221
|
-
public void setOrderId(String orderId) { this.orderId = orderId; }
|
222
|
-
public Map<String, String> getCustomer() { return customer; }
|
223
|
-
public void setCustomer(Map<String, String> customer) { this.customer = customer; }
|
224
|
-
public Map<String, Object> getDelivery() { return delivery; }
|
225
|
-
public void setDelivery(Map<String, Object> delivery) { this.delivery = delivery; }
|
226
|
-
public List<Map<String, Object>> getItems() { return items; }
|
227
|
-
public void setItems(List<Map<String, Object>> items) { this.items = items; }
|
228
|
-
public Map<String, Double> getTotals() { return totals; }
|
229
|
-
public void setTotals(Map<String, Double> totals) { this.totals = totals; }
|
230
|
-
public Map<String, String> getPayment() { return payment; }
|
231
|
-
public void setPayment(Map<String, String> payment) { this.payment = payment; }
|
232
|
-
}
|
233
|
-
|
234
|
-
public static class WebhookResponse {
|
235
|
-
private boolean accepted;
|
236
|
-
private String orderId;
|
237
|
-
private String partnerOrderId;
|
238
|
-
private String message;
|
239
|
-
private String estimatedReadyTime;
|
240
|
-
|
241
|
-
public WebhookResponse(boolean accepted, String orderId, String partnerOrderId,
|
242
|
-
String message, String estimatedReadyTime) {
|
243
|
-
this.accepted = accepted;
|
244
|
-
this.orderId = orderId;
|
245
|
-
this.partnerOrderId = partnerOrderId;
|
246
|
-
this.message = message;
|
247
|
-
this.estimatedReadyTime = estimatedReadyTime;
|
248
|
-
}
|
249
|
-
|
250
|
-
// Getters
|
251
|
-
public boolean isAccepted() { return accepted; }
|
252
|
-
public String getOrderId() { return orderId; }
|
253
|
-
public String getPartnerOrderId() { return partnerOrderId; }
|
254
|
-
public String getMessage() { return message; }
|
255
|
-
public String getEstimatedReadyTime() { return estimatedReadyTime; }
|
256
|
-
}
|
257
|
-
|
258
|
-
public static void main(String[] args) {
|
259
|
-
SpringApplication.run(GrubtechOrderWebhook.class, args);
|
260
|
-
System.out.println("✅ Webhook server started");
|
261
|
-
System.out.println("Webhook URL: http://localhost:8080/webhooks/grubtech/orders");
|
262
|
-
}
|
263
|
-
}
|
@@ -1,157 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Grubtech Order Receive Webhook - Python (Flask)
|
3
|
-
|
4
|
-
This example demonstrates how to receive orders from Grubtech
|
5
|
-
via webhook and process them in your system.
|
6
|
-
|
7
|
-
Prerequisites:
|
8
|
-
- Python 3.8+
|
9
|
-
- Flask (pip install flask)
|
10
|
-
|
11
|
-
Replace the following placeholders:
|
12
|
-
- {{WEBHOOK_SECRET}}: Your webhook secret for signature verification
|
13
|
-
- {{ORDER_PROCESSING_LOGIC}}: Your business logic to process the order
|
14
|
-
"""
|
15
|
-
|
16
|
-
from flask import Flask, request, jsonify
|
17
|
-
import hmac
|
18
|
-
import hashlib
|
19
|
-
import time
|
20
|
-
from datetime import datetime, timedelta
|
21
|
-
from typing import Dict, Any, List
|
22
|
-
|
23
|
-
app = Flask(__name__)
|
24
|
-
WEBHOOK_SECRET = '{{WEBHOOK_SECRET}}'
|
25
|
-
|
26
|
-
|
27
|
-
def verify_signature(data: bytes, signature: str) -> bool:
|
28
|
-
"""
|
29
|
-
Verify webhook signature using HMAC-SHA256
|
30
|
-
|
31
|
-
Args:
|
32
|
-
data: Raw request body
|
33
|
-
signature: Signature from X-Grubtech-Signature header
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
bool: True if signature is valid
|
37
|
-
"""
|
38
|
-
if not signature:
|
39
|
-
print('❌ No signature provided')
|
40
|
-
return False
|
41
|
-
|
42
|
-
# Calculate expected signature
|
43
|
-
expected_signature = hmac.new(
|
44
|
-
WEBHOOK_SECRET.encode('utf-8'),
|
45
|
-
data,
|
46
|
-
hashlib.sha256
|
47
|
-
).hexdigest()
|
48
|
-
|
49
|
-
# Constant-time comparison
|
50
|
-
is_valid = hmac.compare_digest(signature, expected_signature)
|
51
|
-
|
52
|
-
if not is_valid:
|
53
|
-
print('❌ Invalid signature')
|
54
|
-
|
55
|
-
return is_valid
|
56
|
-
|
57
|
-
|
58
|
-
def process_order(order: Dict[str, Any]) -> Dict[str, Any]:
|
59
|
-
"""
|
60
|
-
Process order (replace with your business logic)
|
61
|
-
|
62
|
-
Args:
|
63
|
-
order: Order data from webhook
|
64
|
-
|
65
|
-
Returns:
|
66
|
-
Dict: Response to send back to Grubtech
|
67
|
-
"""
|
68
|
-
try:
|
69
|
-
order_id = order['orderId']
|
70
|
-
customer_name = order['customer']['name']
|
71
|
-
items_count = len(order['items'])
|
72
|
-
total = order['totals']['total']
|
73
|
-
|
74
|
-
print(f'📦 Processing order: {order_id}')
|
75
|
-
print(f'Customer: {customer_name}')
|
76
|
-
print(f'Items: {items_count}')
|
77
|
-
print(f'Total: ${total}')
|
78
|
-
|
79
|
-
# {{ORDER_PROCESSING_LOGIC}}
|
80
|
-
# Example: Save to database, notify kitchen, etc.
|
81
|
-
|
82
|
-
# Simulate processing
|
83
|
-
time.sleep(0.1)
|
84
|
-
|
85
|
-
# Calculate estimated ready time (30 minutes from now)
|
86
|
-
ready_time = (datetime.utcnow() + timedelta(minutes=30)).isoformat() + 'Z'
|
87
|
-
|
88
|
-
return {
|
89
|
-
'accepted': True,
|
90
|
-
'orderId': order_id,
|
91
|
-
'partnerOrderId': f'POS-{int(time.time())}',
|
92
|
-
'message': 'Order accepted successfully',
|
93
|
-
'estimatedReadyTime': ready_time,
|
94
|
-
}
|
95
|
-
|
96
|
-
except Exception as e:
|
97
|
-
print(f'❌ Order processing error: {e}')
|
98
|
-
|
99
|
-
return {
|
100
|
-
'accepted': False,
|
101
|
-
'orderId': order.get('orderId', 'unknown'),
|
102
|
-
'message': f'Order rejected: {str(e)}',
|
103
|
-
}
|
104
|
-
|
105
|
-
|
106
|
-
@app.route('/webhooks/grubtech/orders', methods=['POST'])
|
107
|
-
def receive_order():
|
108
|
-
"""
|
109
|
-
Webhook endpoint to receive orders from Grubtech
|
110
|
-
"""
|
111
|
-
try:
|
112
|
-
# Get raw request data and signature
|
113
|
-
raw_data = request.get_data()
|
114
|
-
signature = request.headers.get('X-Grubtech-Signature', '')
|
115
|
-
|
116
|
-
# Verify signature
|
117
|
-
if not verify_signature(raw_data, signature):
|
118
|
-
return jsonify({
|
119
|
-
'accepted': False,
|
120
|
-
'message': 'Invalid signature',
|
121
|
-
}), 401
|
122
|
-
|
123
|
-
# Parse order data
|
124
|
-
order = request.get_json()
|
125
|
-
|
126
|
-
# Validate order data
|
127
|
-
if not order.get('orderId') or not order.get('items') or len(order['items']) == 0:
|
128
|
-
return jsonify({
|
129
|
-
'accepted': False,
|
130
|
-
'message': 'Invalid order data',
|
131
|
-
}), 400
|
132
|
-
|
133
|
-
# Process order
|
134
|
-
response = process_order(order)
|
135
|
-
|
136
|
-
# Send response
|
137
|
-
status_code = 200 if response['accepted'] else 400
|
138
|
-
return jsonify(response), status_code
|
139
|
-
|
140
|
-
except Exception as e:
|
141
|
-
print(f'❌ Webhook error: {e}')
|
142
|
-
return jsonify({
|
143
|
-
'accepted': False,
|
144
|
-
'message': 'Internal server error',
|
145
|
-
}), 500
|
146
|
-
|
147
|
-
|
148
|
-
@app.route('/health', methods=['GET'])
|
149
|
-
def health_check():
|
150
|
-
"""Health check endpoint"""
|
151
|
-
return jsonify({'status': 'ok'})
|
152
|
-
|
153
|
-
|
154
|
-
if __name__ == '__main__':
|
155
|
-
print('✅ Webhook server starting...')
|
156
|
-
print('Webhook URL: http://localhost:5000/webhooks/grubtech/orders')
|
157
|
-
app.run(host='0.0.0.0', port=5000, debug=True)
|
@@ -1,194 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Grubtech Order Receive Webhook - TypeScript (Express.js)
|
3
|
-
*
|
4
|
-
* This example demonstrates how to receive orders from Grubtech
|
5
|
-
* via webhook and process them in your system.
|
6
|
-
*
|
7
|
-
* Prerequisites:
|
8
|
-
* - Node.js 18+
|
9
|
-
* - Express.js (npm install express)
|
10
|
-
* - crypto module (built-in)
|
11
|
-
*
|
12
|
-
* Replace the following placeholders:
|
13
|
-
* - {{WEBHOOK_SECRET}}: Your webhook secret for signature verification
|
14
|
-
* - {{ORDER_PROCESSING_LOGIC}}: Your business logic to process the order
|
15
|
-
*/
|
16
|
-
|
17
|
-
import express, { Request, Response } from 'express';
|
18
|
-
import crypto from 'crypto';
|
19
|
-
|
20
|
-
const app = express();
|
21
|
-
const PORT = 3000;
|
22
|
-
const WEBHOOK_SECRET = '{{WEBHOOK_SECRET}}';
|
23
|
-
|
24
|
-
// Middleware to parse JSON and preserve raw body for signature verification
|
25
|
-
app.use(
|
26
|
-
express.json({
|
27
|
-
verify: (req: any, res, buf) => {
|
28
|
-
req.rawBody = buf.toString('utf8');
|
29
|
-
},
|
30
|
-
})
|
31
|
-
);
|
32
|
-
|
33
|
-
interface OrderItem {
|
34
|
-
id: string;
|
35
|
-
name: string;
|
36
|
-
quantity: number;
|
37
|
-
price: number;
|
38
|
-
modifiers?: Array<{ id: string; name: string; price: number }>;
|
39
|
-
}
|
40
|
-
|
41
|
-
interface Order {
|
42
|
-
orderId: string;
|
43
|
-
partnerOrderId?: string;
|
44
|
-
status: string;
|
45
|
-
createdAt: string;
|
46
|
-
customer: {
|
47
|
-
name: string;
|
48
|
-
phone: string;
|
49
|
-
email?: string;
|
50
|
-
};
|
51
|
-
delivery: {
|
52
|
-
type: 'delivery' | 'pickup';
|
53
|
-
address?: string;
|
54
|
-
city?: string;
|
55
|
-
instructions?: string;
|
56
|
-
};
|
57
|
-
items: OrderItem[];
|
58
|
-
totals: {
|
59
|
-
subtotal: number;
|
60
|
-
tax: number;
|
61
|
-
deliveryFee: number;
|
62
|
-
total: number;
|
63
|
-
};
|
64
|
-
payment: {
|
65
|
-
method: string;
|
66
|
-
status: string;
|
67
|
-
};
|
68
|
-
}
|
69
|
-
|
70
|
-
interface WebhookResponse {
|
71
|
-
accepted: boolean;
|
72
|
-
orderId?: string;
|
73
|
-
partnerOrderId?: string;
|
74
|
-
message?: string;
|
75
|
-
estimatedReadyTime?: string;
|
76
|
-
}
|
77
|
-
|
78
|
-
/**
|
79
|
-
* Verify webhook signature
|
80
|
-
*/
|
81
|
-
function verifySignature(req: any): boolean {
|
82
|
-
const signature = req.headers['x-grubtech-signature'];
|
83
|
-
|
84
|
-
if (!signature) {
|
85
|
-
console.error('❌ No signature provided');
|
86
|
-
return false;
|
87
|
-
}
|
88
|
-
|
89
|
-
// Calculate expected signature using HMAC-SHA256
|
90
|
-
const expectedSignature = crypto
|
91
|
-
.createHmac('sha256', WEBHOOK_SECRET)
|
92
|
-
.update(req.rawBody)
|
93
|
-
.digest('hex');
|
94
|
-
|
95
|
-
// Constant-time comparison to prevent timing attacks
|
96
|
-
const isValid = crypto.timingSafeEqual(
|
97
|
-
Buffer.from(signature),
|
98
|
-
Buffer.from(expectedSignature)
|
99
|
-
);
|
100
|
-
|
101
|
-
if (!isValid) {
|
102
|
-
console.error('❌ Invalid signature');
|
103
|
-
}
|
104
|
-
|
105
|
-
return isValid;
|
106
|
-
}
|
107
|
-
|
108
|
-
/**
|
109
|
-
* Process order (replace with your business logic)
|
110
|
-
*/
|
111
|
-
async function processOrder(order: Order): Promise<WebhookResponse> {
|
112
|
-
try {
|
113
|
-
console.log(`📦 Processing order: ${order.orderId}`);
|
114
|
-
console.log(`Customer: ${order.customer.name}`);
|
115
|
-
console.log(`Items: ${order.items.length}`);
|
116
|
-
console.log(`Total: $${order.totals.total}`);
|
117
|
-
|
118
|
-
// {{ORDER_PROCESSING_LOGIC}}
|
119
|
-
// Example: Save to database, notify kitchen, etc.
|
120
|
-
|
121
|
-
// Simulate processing time
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
123
|
-
|
124
|
-
// Calculate estimated ready time (example: 30 minutes from now)
|
125
|
-
const readyTime = new Date(Date.now() + 30 * 60 * 1000).toISOString();
|
126
|
-
|
127
|
-
return {
|
128
|
-
accepted: true,
|
129
|
-
orderId: order.orderId,
|
130
|
-
partnerOrderId: `POS-${Date.now()}`,
|
131
|
-
message: 'Order accepted successfully',
|
132
|
-
estimatedReadyTime: readyTime,
|
133
|
-
};
|
134
|
-
} catch (error) {
|
135
|
-
console.error('❌ Order processing error:', error);
|
136
|
-
|
137
|
-
return {
|
138
|
-
accepted: false,
|
139
|
-
orderId: order.orderId,
|
140
|
-
message: `Order rejected: ${error}`,
|
141
|
-
};
|
142
|
-
}
|
143
|
-
}
|
144
|
-
|
145
|
-
/**
|
146
|
-
* Webhook endpoint to receive orders from Grubtech
|
147
|
-
*/
|
148
|
-
app.post('/webhooks/grubtech/orders', async (req: Request, res: Response) => {
|
149
|
-
try {
|
150
|
-
// Verify signature
|
151
|
-
if (!verifySignature(req)) {
|
152
|
-
return res.status(401).json({
|
153
|
-
accepted: false,
|
154
|
-
message: 'Invalid signature',
|
155
|
-
});
|
156
|
-
}
|
157
|
-
|
158
|
-
const order: Order = req.body;
|
159
|
-
|
160
|
-
// Validate order data
|
161
|
-
if (!order.orderId || !order.items || order.items.length === 0) {
|
162
|
-
return res.status(400).json({
|
163
|
-
accepted: false,
|
164
|
-
message: 'Invalid order data',
|
165
|
-
});
|
166
|
-
}
|
167
|
-
|
168
|
-
// Process order
|
169
|
-
const response = await processOrder(order);
|
170
|
-
|
171
|
-
// Send response
|
172
|
-
const statusCode = response.accepted ? 200 : 400;
|
173
|
-
return res.status(statusCode).json(response);
|
174
|
-
} catch (error) {
|
175
|
-
console.error('❌ Webhook error:', error);
|
176
|
-
return res.status(500).json({
|
177
|
-
accepted: false,
|
178
|
-
message: 'Internal server error',
|
179
|
-
});
|
180
|
-
}
|
181
|
-
});
|
182
|
-
|
183
|
-
/**
|
184
|
-
* Health check endpoint
|
185
|
-
*/
|
186
|
-
app.get('/health', (req: Request, res: Response) => {
|
187
|
-
res.json({ status: 'ok' });
|
188
|
-
});
|
189
|
-
|
190
|
-
// Start server
|
191
|
-
app.listen(PORT, () => {
|
192
|
-
console.log(`✅ Webhook server listening on port ${PORT}`);
|
193
|
-
console.log(`Webhook URL: http://localhost:${PORT}/webhooks/grubtech/orders`);
|
194
|
-
});
|