food402 1.0.4-beta.1 → 1.1.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/dist/api.js DELETED
@@ -1,1164 +0,0 @@
1
- // src/api.ts - TGO Yemek API Functions
2
- import { getToken } from "./auth.js";
3
- import { randomUUID } from "crypto";
4
- const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36";
5
- const API_BASE = "https://api.tgoapis.com";
6
- const PAYMENT_API_BASE = "https://payment.tgoapps.com";
7
- export async function getAddresses() {
8
- const token = await getToken();
9
- const response = await fetch(`${API_BASE}/web-user-apimemberaddress-santral/addresses`, {
10
- method: "GET",
11
- headers: {
12
- "Accept": "application/json, text/plain, */*",
13
- "Authorization": `Bearer ${token}`,
14
- "User-Agent": USER_AGENT,
15
- "Origin": "https://tgoyemek.com",
16
- "x-correlationid": randomUUID(),
17
- "pid": randomUUID(),
18
- "sid": randomUUID()
19
- }
20
- });
21
- if (!response.ok) {
22
- throw new Error(`Failed to fetch addresses: ${response.status} ${response.statusText}`);
23
- }
24
- return response.json();
25
- }
26
- export async function getRestaurants(latitude, longitude, page = 1) {
27
- const token = await getToken();
28
- const pageSize = 50;
29
- const params = new URLSearchParams({
30
- sortType: "RESTAURANT_SCORE",
31
- minBasketPrice: "400",
32
- openRestaurants: "true",
33
- latitude,
34
- longitude,
35
- pageSize: pageSize.toString(),
36
- page: page.toString()
37
- });
38
- const response = await fetch(`${API_BASE}/web-discovery-apidiscovery-santral/restaurants/filters?${params}`, {
39
- method: "GET",
40
- headers: {
41
- "Accept": "application/json, text/plain, */*",
42
- "Authorization": `Bearer ${token}`,
43
- "User-Agent": USER_AGENT,
44
- "Origin": "https://tgoyemek.com",
45
- "x-correlationid": randomUUID(),
46
- "pid": randomUUID(),
47
- "sid": randomUUID()
48
- }
49
- });
50
- if (!response.ok) {
51
- throw new Error(`Failed to fetch restaurants: ${response.status} ${response.statusText}`);
52
- }
53
- const data = await response.json();
54
- // Transform to simplified format for AI context efficiency
55
- const restaurants = data.restaurants.map((r) => ({
56
- id: r.id,
57
- name: r.name,
58
- kitchen: r.kitchen,
59
- rating: r.rating,
60
- ratingText: r.ratingText,
61
- minBasketPrice: r.minBasketPrice,
62
- averageDeliveryInterval: r.averageDeliveryInterval,
63
- distance: r.location?.distance ?? 0,
64
- neighborhoodName: r.location?.neighborhoodName ?? "",
65
- isClosed: r.isClosed,
66
- campaignText: r.campaignText
67
- }));
68
- return {
69
- restaurants,
70
- totalCount: data.restaurantCount,
71
- currentPage: page,
72
- pageSize,
73
- hasNextPage: !!data.links?.next?.href
74
- };
75
- }
76
- export async function getRestaurantMenu(restaurantId, latitude, longitude) {
77
- const token = await getToken();
78
- const params = new URLSearchParams({ latitude, longitude });
79
- const response = await fetch(`${API_BASE}/web-restaurant-apirestaurant-santral/restaurants/${restaurantId}?${params}`, {
80
- method: "GET",
81
- headers: {
82
- "Accept": "application/json, text/plain, */*",
83
- "Authorization": `Bearer ${token}`,
84
- "User-Agent": USER_AGENT,
85
- "Origin": "https://tgoyemek.com",
86
- "x-correlationid": randomUUID(),
87
- "pid": randomUUID(),
88
- "sid": randomUUID()
89
- }
90
- });
91
- if (!response.ok) {
92
- throw new Error(`Failed to fetch restaurant menu: ${response.status} ${response.statusText}`);
93
- }
94
- const data = await response.json();
95
- const restaurant = data.restaurant;
96
- // Transform restaurant info
97
- const info = {
98
- id: restaurant.info.id,
99
- name: restaurant.info.name,
100
- status: restaurant.info.status,
101
- rating: restaurant.info.score?.overall ?? 0,
102
- ratingText: restaurant.info.score?.ratingText ?? "",
103
- workingHours: restaurant.info.workingHours,
104
- deliveryTime: restaurant.info.deliveryInfo?.eta ?? "",
105
- minOrderPrice: restaurant.info.deliveryInfo?.minPrice ?? 0
106
- };
107
- // Transform categories and products
108
- let totalItems = 0;
109
- const categories = restaurant.sections.map((section) => {
110
- const items = section.products.map((product) => ({
111
- id: product.id,
112
- name: product.name,
113
- description: product.description ?? "",
114
- price: product.price?.salePrice ?? 0,
115
- likePercentage: product.productScore?.likePercentageInfo
116
- }));
117
- totalItems += items.length;
118
- return {
119
- name: section.name,
120
- slug: section.slug,
121
- items
122
- };
123
- });
124
- return {
125
- info,
126
- categories,
127
- totalItems
128
- };
129
- }
130
- export async function getProductRecommendations(restaurantId, productIds) {
131
- const token = await getToken();
132
- const response = await fetch(`${API_BASE}/web-discovery-apidiscovery-santral/recommendation/product`, {
133
- method: "POST",
134
- headers: {
135
- "Accept": "application/json, text/plain, */*",
136
- "Authorization": `Bearer ${token}`,
137
- "Content-Type": "application/json",
138
- "User-Agent": USER_AGENT,
139
- "Origin": "https://tgoyemek.com",
140
- "x-correlationid": randomUUID(),
141
- "pid": randomUUID(),
142
- "sid": randomUUID()
143
- },
144
- body: JSON.stringify({
145
- restaurantId: restaurantId.toString(),
146
- productIds: productIds.map(id => id.toString()),
147
- page: "PDP"
148
- })
149
- });
150
- if (!response.ok) {
151
- throw new Error(`Failed to fetch product recommendations: ${response.status} ${response.statusText}`);
152
- }
153
- const data = await response.json();
154
- // Transform to simplified format
155
- let totalItems = 0;
156
- const collections = (data.collections || []).map((collection) => {
157
- const items = (collection.items || []).map((item) => ({
158
- id: item.id,
159
- name: item.name,
160
- description: item.description,
161
- price: item.sellingPrice ?? 0,
162
- imageUrl: item.imageUrl ?? ""
163
- }));
164
- totalItems += items.length;
165
- return {
166
- name: collection.name,
167
- items
168
- };
169
- });
170
- return {
171
- collections,
172
- totalItems
173
- };
174
- }
175
- export async function getProductDetails(restaurantId, productId, latitude, longitude) {
176
- const token = await getToken();
177
- const params = new URLSearchParams({ latitude, longitude });
178
- const response = await fetch(`${API_BASE}/web-restaurant-apirestaurant-santral/restaurants/${restaurantId}/products/${productId}?${params}`, {
179
- method: "POST",
180
- headers: {
181
- "Accept": "application/json, text/plain, */*",
182
- "Authorization": `Bearer ${token}`,
183
- "Content-Type": "application/json",
184
- "User-Agent": USER_AGENT,
185
- "Origin": "https://tgoyemek.com",
186
- "x-correlationid": randomUUID(),
187
- "pid": randomUUID(),
188
- "sid": randomUUID()
189
- },
190
- body: JSON.stringify({})
191
- });
192
- if (!response.ok) {
193
- throw new Error(`Failed to fetch product details: ${response.status} ${response.statusText}`);
194
- }
195
- const data = await response.json();
196
- // Transform components to simplified format
197
- const components = (data.components || []).map((comp) => ({
198
- type: comp.type,
199
- title: comp.title,
200
- description: comp.description,
201
- modifierGroupId: comp.modifierGroupId, // Include for MODIFIER_GROUP types
202
- options: (comp.options || []).map((opt) => ({
203
- id: opt.optionId,
204
- name: opt.title,
205
- price: opt.price?.salePrice ?? 0,
206
- selected: opt.selected ?? false,
207
- isPopular: opt.badges?.some((b) => b.type === "POPULAR_OPTION") ?? false
208
- })),
209
- isSingleChoice: comp.isSingleChoice ?? false,
210
- minSelections: comp.min ?? 0,
211
- maxSelections: comp.max ?? 0
212
- }));
213
- return {
214
- restaurantId: data.restaurantId,
215
- restaurantName: data.restaurantName,
216
- productId: data.productId,
217
- productName: data.productName,
218
- description: data.productDescription ?? "",
219
- imageUrl: data.productImage ?? "",
220
- price: data.price?.salePrice ?? 0,
221
- maxQuantity: data.maxQuantity ?? 50,
222
- components
223
- };
224
- }
225
- export async function setShippingAddress(request) {
226
- const token = await getToken();
227
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/shipping`, {
228
- method: "POST",
229
- headers: {
230
- "Accept": "application/json, text/plain, */*",
231
- "Authorization": `Bearer ${token}`,
232
- "Content-Type": "application/json",
233
- "User-Agent": USER_AGENT,
234
- "Origin": "https://tgoyemek.com",
235
- "x-correlationid": randomUUID(),
236
- "pid": randomUUID(),
237
- "sid": randomUUID()
238
- },
239
- body: JSON.stringify(request)
240
- });
241
- if (!response.ok) {
242
- throw new Error(`Failed to set shipping address: ${response.status} ${response.statusText}`);
243
- }
244
- }
245
- export async function addToBasket(request) {
246
- const token = await getToken();
247
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts/items`, {
248
- method: "POST",
249
- headers: {
250
- "Accept": "application/json, text/plain, */*",
251
- "Authorization": `Bearer ${token}`,
252
- "Content-Type": "application/json",
253
- "User-Agent": USER_AGENT,
254
- "Origin": "https://tgoyemek.com",
255
- "x-correlationid": randomUUID(),
256
- "pid": randomUUID(),
257
- "sid": randomUUID()
258
- },
259
- body: JSON.stringify({
260
- storeId: request.storeId,
261
- items: request.items,
262
- isFlashSale: false, // Always false
263
- storePickup: false, // Always false (delivery mode)
264
- latitude: request.latitude,
265
- longitude: request.longitude
266
- })
267
- });
268
- if (!response.ok) {
269
- throw new Error(`Failed to add to basket: ${response.status} ${response.statusText}`);
270
- }
271
- const data = await response.json();
272
- // Extract store info from first grouped product
273
- const storeData = data.groupedProducts?.[0]?.store;
274
- const store = {
275
- id: storeData?.id ?? request.storeId,
276
- name: storeData?.name ?? "",
277
- imageUrl: storeData?.imageUrl ?? "",
278
- rating: storeData?.rating ?? 0,
279
- averageDeliveryInterval: storeData?.averageDeliveryInterval ?? "",
280
- minAmount: storeData?.minAmount ?? 0
281
- };
282
- // Extract products from grouped products
283
- const products = (data.groupedProducts?.[0]?.products || []).map((p) => ({
284
- productId: p.productId,
285
- itemId: p.itemId,
286
- name: p.name,
287
- quantity: p.quantity,
288
- salePrice: p.salePrice,
289
- description: p.description ?? ""
290
- }));
291
- // Transform summary
292
- const summary = (data.summary || []).map((s) => ({
293
- title: s.title,
294
- amount: s.amount,
295
- isPromotion: s.isPromotion ?? false
296
- }));
297
- return {
298
- store,
299
- products,
300
- summary,
301
- totalProductCount: data.totalProductCount ?? 0,
302
- totalProductPrice: data.totalProductPrice ?? 0,
303
- totalProductPriceDiscounted: data.totalProductPriceDiscounted ?? 0,
304
- totalPrice: data.totalPrice ?? 0,
305
- deliveryPrice: data.deliveryPrice ?? 0
306
- };
307
- }
308
- export async function getBasket() {
309
- const token = await getToken();
310
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts`, {
311
- method: "GET",
312
- headers: {
313
- "Accept": "application/json, text/plain, */*",
314
- "Authorization": `Bearer ${token}`,
315
- "User-Agent": USER_AGENT,
316
- "Origin": "https://tgoyemek.com",
317
- "x-correlationid": randomUUID(),
318
- "pid": randomUUID(),
319
- "sid": randomUUID()
320
- }
321
- });
322
- if (!response.ok) {
323
- throw new Error(`Failed to get basket: ${response.status} ${response.statusText}`);
324
- }
325
- const data = await response.json();
326
- // Transform grouped products to store groups
327
- const storeGroups = (data.groupedProducts || []).map((group) => ({
328
- store: {
329
- id: group.store?.id ?? 0,
330
- name: group.store?.name ?? "",
331
- imageUrl: group.store?.imageUrl ?? "",
332
- rating: group.store?.rating ?? 0,
333
- averageDeliveryInterval: group.store?.averageDeliveryInterval ?? "",
334
- minAmount: group.store?.minAmount ?? 0
335
- },
336
- products: (group.products || []).map((p) => ({
337
- productId: p.productId,
338
- itemId: p.itemId,
339
- name: p.name,
340
- quantity: p.quantity,
341
- salePrice: p.salePrice,
342
- description: p.description ?? "",
343
- marketPrice: p.marketPrice ?? 0,
344
- modifierProducts: (p.modifierProducts || []).map((m) => ({
345
- productId: m.productId,
346
- modifierGroupId: m.modifierGroupId,
347
- name: m.name,
348
- price: m.price
349
- })),
350
- ingredientExcludes: (p.ingredientOption?.excludes || []).map((e) => ({
351
- id: e.id,
352
- name: e.name
353
- }))
354
- }))
355
- }));
356
- // Transform summary
357
- const summary = (data.summary || []).map((s) => ({
358
- title: s.title,
359
- amount: s.amount,
360
- isPromotion: s.isPromotion ?? false
361
- }));
362
- return {
363
- storeGroups,
364
- summary,
365
- totalProductCount: data.totalProductCount ?? 0,
366
- totalProductPrice: data.totalProductPrice ?? 0,
367
- totalProductPriceDiscounted: data.totalProductPriceDiscounted ?? 0,
368
- totalPrice: data.totalPrice ?? 0,
369
- deliveryPrice: data.deliveryPrice ?? 0,
370
- isEmpty: (data.totalProductCount ?? 0) === 0
371
- };
372
- }
373
- export async function removeFromBasket(itemId) {
374
- const token = await getToken();
375
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts/items/${itemId}`, {
376
- method: "DELETE",
377
- headers: {
378
- "Accept": "application/json, text/plain, */*",
379
- "Authorization": `Bearer ${token}`,
380
- "User-Agent": USER_AGENT,
381
- "Origin": "https://tgoyemek.com",
382
- "x-correlationid": randomUUID(),
383
- "pid": randomUUID(),
384
- "sid": randomUUID()
385
- }
386
- });
387
- if (!response.ok) {
388
- throw new Error(`Failed to remove from basket: ${response.status} ${response.statusText}`);
389
- }
390
- const data = await response.json();
391
- // Transform using same logic as getBasket
392
- const storeGroups = (data.groupedProducts || []).map((group) => ({
393
- store: {
394
- id: group.store?.id ?? 0,
395
- name: group.store?.name ?? "",
396
- imageUrl: group.store?.imageUrl ?? "",
397
- rating: group.store?.rating ?? 0,
398
- averageDeliveryInterval: group.store?.averageDeliveryInterval ?? "",
399
- minAmount: group.store?.minAmount ?? 0
400
- },
401
- products: (group.products || []).map((p) => ({
402
- productId: p.productId,
403
- itemId: p.itemId,
404
- name: p.name,
405
- quantity: p.quantity,
406
- salePrice: p.salePrice,
407
- description: p.description ?? "",
408
- marketPrice: p.marketPrice ?? 0,
409
- modifierProducts: (p.modifierProducts || []).map((m) => ({
410
- productId: m.productId,
411
- modifierGroupId: m.modifierGroupId,
412
- name: m.name,
413
- price: m.price
414
- })),
415
- ingredientExcludes: (p.ingredientOption?.excludes || []).map((e) => ({
416
- id: e.id,
417
- name: e.name
418
- }))
419
- }))
420
- }));
421
- const summary = (data.summary || []).map((s) => ({
422
- title: s.title,
423
- amount: s.amount,
424
- isPromotion: s.isPromotion ?? false
425
- }));
426
- return {
427
- storeGroups,
428
- summary,
429
- totalProductCount: data.totalProductCount ?? 0,
430
- totalProductPrice: data.totalProductPrice ?? 0,
431
- totalProductPriceDiscounted: data.totalProductPriceDiscounted ?? 0,
432
- totalPrice: data.totalPrice ?? 0,
433
- deliveryPrice: data.deliveryPrice ?? 0,
434
- isEmpty: (data.totalProductCount ?? 0) === 0
435
- };
436
- }
437
- export async function clearBasket() {
438
- const token = await getToken();
439
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts`, {
440
- method: "DELETE",
441
- headers: {
442
- "Accept": "application/json, text/plain, */*",
443
- "Authorization": `Bearer ${token}`,
444
- "User-Agent": USER_AGENT,
445
- "Origin": "https://tgoyemek.com",
446
- "x-correlationid": randomUUID(),
447
- "pid": randomUUID(),
448
- "sid": randomUUID()
449
- }
450
- });
451
- if (!response.ok) {
452
- throw new Error(`Failed to clear basket: ${response.status} ${response.statusText}`);
453
- }
454
- }
455
- export async function getCities() {
456
- const token = await getToken();
457
- const response = await fetch(`${API_BASE}/web-user-apimemberaddress-santral/cities`, {
458
- method: "GET",
459
- headers: {
460
- "Accept": "application/json, text/plain, */*",
461
- "Authorization": `Bearer ${token}`,
462
- "User-Agent": USER_AGENT,
463
- "Origin": "https://tgoyemek.com",
464
- "x-correlationid": randomUUID(),
465
- "pid": randomUUID(),
466
- "sid": randomUUID()
467
- }
468
- });
469
- if (!response.ok) {
470
- throw new Error(`Failed to fetch cities: ${response.status} ${response.statusText}`);
471
- }
472
- const data = await response.json();
473
- const cities = (data.cities || []).map((c) => ({
474
- id: c.id,
475
- code: c.code,
476
- name: c.name
477
- }));
478
- return {
479
- cities,
480
- count: cities.length
481
- };
482
- }
483
- export async function getDistricts(cityId) {
484
- const token = await getToken();
485
- const response = await fetch(`${API_BASE}/web-user-apimemberaddress-santral/cities/${cityId}/districts`, {
486
- method: "GET",
487
- headers: {
488
- "Accept": "application/json, text/plain, */*",
489
- "Authorization": `Bearer ${token}`,
490
- "User-Agent": USER_AGENT,
491
- "Origin": "https://tgoyemek.com",
492
- "x-correlationid": randomUUID(),
493
- "pid": randomUUID(),
494
- "sid": randomUUID()
495
- }
496
- });
497
- if (!response.ok) {
498
- throw new Error(`Failed to fetch districts: ${response.status} ${response.statusText}`);
499
- }
500
- const data = await response.json();
501
- const districts = (data.districts || []).map((d) => ({
502
- id: d.id,
503
- name: d.name
504
- }));
505
- return {
506
- districts,
507
- count: districts.length,
508
- cityId
509
- };
510
- }
511
- export async function getNeighborhoods(districtId) {
512
- const token = await getToken();
513
- const response = await fetch(`${API_BASE}/web-user-apimemberaddress-santral/districts/${districtId}/neighborhoods`, {
514
- method: "GET",
515
- headers: {
516
- "Accept": "application/json, text/plain, */*",
517
- "Authorization": `Bearer ${token}`,
518
- "User-Agent": USER_AGENT,
519
- "Origin": "https://tgoyemek.com",
520
- "x-correlationid": randomUUID(),
521
- "pid": randomUUID(),
522
- "sid": randomUUID()
523
- }
524
- });
525
- if (!response.ok) {
526
- throw new Error(`Failed to fetch neighborhoods: ${response.status} ${response.statusText}`);
527
- }
528
- const data = await response.json();
529
- const neighborhoods = (data.neighborhoods || []).map((n) => ({
530
- id: n.id,
531
- name: n.name
532
- }));
533
- return {
534
- neighborhoods,
535
- count: neighborhoods.length,
536
- districtId
537
- };
538
- }
539
- export async function addAddress(request) {
540
- const token = await getToken();
541
- const payload = {
542
- name: request.name,
543
- surname: request.surname,
544
- phone: request.phone,
545
- apartmentNumber: request.apartmentNumber ?? "",
546
- floor: request.floor ?? "",
547
- doorNumber: request.doorNumber ?? "",
548
- addressName: request.addressName,
549
- addressDescription: request.addressDescription ?? "",
550
- addressLine: request.addressLine,
551
- cityId: request.cityId,
552
- districtId: request.districtId,
553
- neighborhoodId: request.neighborhoodId,
554
- latitude: request.latitude,
555
- longitude: request.longitude,
556
- countryCode: request.countryCode ?? "TR",
557
- elevatorAvailable: request.elevatorAvailable ?? false
558
- };
559
- const response = await fetch(`${API_BASE}/web-user-apimemberaddress-santral/addresses`, {
560
- method: "POST",
561
- headers: {
562
- "Accept": "application/json, text/plain, */*",
563
- "Authorization": `Bearer ${token}`,
564
- "Content-Type": "application/json",
565
- "User-Agent": USER_AGENT,
566
- "Origin": "https://tgoyemek.com",
567
- "x-correlationid": randomUUID(),
568
- "pid": randomUUID(),
569
- "sid": randomUUID()
570
- },
571
- body: JSON.stringify(payload)
572
- });
573
- // Handle OTP required case (429 Too Many Requests)
574
- if (response.status === 429) {
575
- return {
576
- success: false,
577
- requiresOtp: true,
578
- message: "OTP verification required. Please add this address through the TGO Yemek website."
579
- };
580
- }
581
- if (!response.ok) {
582
- throw new Error(`Failed to add address: ${response.status} ${response.statusText}`);
583
- }
584
- const data = await response.json();
585
- // Transform the returned address
586
- const address = {
587
- id: data.id,
588
- name: data.name,
589
- surname: data.surname,
590
- phone: data.phone,
591
- countryPhoneCode: data.countryPhoneCode ?? "+90",
592
- addressLine: data.addressLine,
593
- addressName: data.addressName,
594
- postalCode: data.postalCode ?? "",
595
- cityId: data.cityId,
596
- cityName: data.cityName ?? "",
597
- districtId: data.districtId,
598
- districtName: data.districtName ?? "",
599
- neighborhoodId: data.neighborhoodId,
600
- neighborhoodName: data.neighborhoodName ?? "",
601
- latitude: data.latitude,
602
- longitude: data.longitude,
603
- addressDescription: data.addressDescription ?? "",
604
- apartmentNumber: data.apartmentNumber ?? "",
605
- floor: data.floor ?? "",
606
- doorNumber: data.doorNumber ?? "",
607
- addressType: data.addressType ?? "HOME",
608
- elevatorAvailable: data.elevatorAvailable ?? false
609
- };
610
- return {
611
- success: true,
612
- address,
613
- message: "Address added successfully"
614
- };
615
- }
616
- export async function updateCustomerNote(request) {
617
- const token = await getToken();
618
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts/customerNote`, {
619
- method: "PUT",
620
- headers: {
621
- "Accept": "application/json, text/plain, */*",
622
- "Authorization": `Bearer ${token}`,
623
- "Content-Type": "application/json",
624
- "User-Agent": USER_AGENT,
625
- "Origin": "https://tgoyemek.com",
626
- "x-correlationid": randomUUID(),
627
- "pid": randomUUID(),
628
- "sid": randomUUID()
629
- },
630
- body: JSON.stringify({
631
- customerNote: request.customerNote,
632
- noServiceWare: request.noServiceWare,
633
- contactlessDelivery: request.contactlessDelivery,
634
- dontRingBell: request.dontRingBell
635
- })
636
- });
637
- if (!response.ok) {
638
- throw new Error(`Failed to update customer note: ${response.status} ${response.statusText}`);
639
- }
640
- }
641
- export async function getSavedCards() {
642
- const token = await getToken();
643
- const response = await fetch(`${PAYMENT_API_BASE}/v2/cards/`, {
644
- method: "GET",
645
- headers: {
646
- "Accept": "application/json, text/plain, */*",
647
- "Authorization": `Bearer ${token}`,
648
- "User-Agent": USER_AGENT,
649
- "Origin": "https://tgoyemek.com",
650
- "app-name": "TrendyolGo",
651
- "x-applicationid": "1",
652
- "x-channelid": "4",
653
- "x-storefrontid": "1",
654
- "x-features": "OPTIONAL_REBATE;MEAL_CART_ENABLED",
655
- "x-supported-payment-options": "MULTINET;SODEXO;EDENRED;ON_DELIVERY;SETCARD",
656
- "x-correlationid": randomUUID(),
657
- "pid": randomUUID(),
658
- "sid": randomUUID()
659
- }
660
- });
661
- if (!response.ok) {
662
- throw new Error(`Failed to fetch saved cards: ${response.status} ${response.statusText}`);
663
- }
664
- const data = await response.json();
665
- const cardsData = data.json?.cards || data.cards || [];
666
- const cards = cardsData.map((c) => ({
667
- cardId: c.cardId,
668
- name: c.name ?? "",
669
- maskedCardNumber: c.maskedCardNumber ?? "",
670
- cardTypeName: c.cardTypeName ?? "",
671
- bankName: c.bankName ?? "",
672
- isDebitCard: c.isDebitCard ?? false,
673
- cvvRequired: c.cvvRequired ?? false,
674
- cardNetwork: c.cardNetwork ?? ""
675
- }));
676
- if (cards.length === 0) {
677
- return {
678
- cards: [],
679
- hasCards: false,
680
- message: "No saved cards. Please add a payment method at tgoyemek.com"
681
- };
682
- }
683
- return {
684
- cards,
685
- hasCards: true
686
- };
687
- }
688
- export async function getCheckoutReady() {
689
- const token = await getToken();
690
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts?cartContext=payment&limitPromoMbs=false`, {
691
- method: "GET",
692
- headers: {
693
- "Accept": "application/json, text/plain, */*",
694
- "Authorization": `Bearer ${token}`,
695
- "User-Agent": USER_AGENT,
696
- "Origin": "https://tgoyemek.com",
697
- "x-correlationid": randomUUID(),
698
- "pid": randomUUID(),
699
- "sid": randomUUID()
700
- }
701
- });
702
- // Handle 400 error (typically means empty cart)
703
- if (response.status === 400) {
704
- return {
705
- ready: false,
706
- store: {
707
- id: 0,
708
- name: "",
709
- imageUrl: "",
710
- rating: 0,
711
- averageDeliveryInterval: "",
712
- minAmount: 0
713
- },
714
- products: [],
715
- summary: [],
716
- totalPrice: 0,
717
- deliveryPrice: 0,
718
- warnings: ["Cart is empty. Add items before checkout."]
719
- };
720
- }
721
- if (!response.ok) {
722
- throw new Error(`Failed to get checkout ready: ${response.status} ${response.statusText}`);
723
- }
724
- const data = await response.json();
725
- // Extract warnings from response
726
- const warnings = [];
727
- if (data.warnings) {
728
- warnings.push(...data.warnings.map((w) => w.message || String(w)));
729
- }
730
- // Check if cart is empty
731
- if ((data.totalProductCount ?? 0) === 0) {
732
- return {
733
- ready: false,
734
- store: {
735
- id: 0,
736
- name: "",
737
- imageUrl: "",
738
- rating: 0,
739
- averageDeliveryInterval: "",
740
- minAmount: 0
741
- },
742
- products: [],
743
- summary: [],
744
- totalPrice: 0,
745
- deliveryPrice: 0,
746
- warnings: ["Cart is empty. Add items before checkout."]
747
- };
748
- }
749
- // Extract store and products from first group
750
- const group = data.groupedProducts?.[0];
751
- const store = {
752
- id: group?.store?.id ?? 0,
753
- name: group?.store?.name ?? "",
754
- imageUrl: group?.store?.imageUrl ?? "",
755
- rating: group?.store?.rating ?? 0,
756
- averageDeliveryInterval: group?.store?.averageDeliveryInterval ?? "",
757
- minAmount: group?.store?.minAmount ?? 0
758
- };
759
- const products = (group?.products || []).map((p) => ({
760
- productId: p.productId,
761
- itemId: p.itemId,
762
- name: p.name,
763
- quantity: p.quantity,
764
- salePrice: p.salePrice,
765
- description: p.description ?? "",
766
- marketPrice: p.marketPrice ?? 0,
767
- modifierProducts: (p.modifierProducts || []).map((m) => ({
768
- productId: m.productId,
769
- modifierGroupId: m.modifierGroupId,
770
- name: m.name,
771
- price: m.price
772
- })),
773
- ingredientExcludes: (p.ingredientOption?.excludes || []).map((e) => ({
774
- id: e.id,
775
- name: e.name
776
- }))
777
- }));
778
- const summary = (data.summary || []).map((s) => ({
779
- title: s.title,
780
- amount: s.amount,
781
- isPromotion: s.isPromotion ?? false
782
- }));
783
- // Check minimum order amount
784
- const minAmount = store.minAmount || 0;
785
- const totalPrice = data.totalPrice ?? 0;
786
- if (minAmount > 0 && totalPrice < minAmount) {
787
- warnings.push(`Minimum order amount is ${minAmount} TL. Current total: ${totalPrice} TL`);
788
- }
789
- return {
790
- ready: warnings.length === 0,
791
- store,
792
- products,
793
- summary,
794
- totalPrice: data.totalPrice ?? 0,
795
- deliveryPrice: data.deliveryPrice ?? 0,
796
- warnings
797
- };
798
- }
799
- async function selectPaymentMethod(cardId, binCode) {
800
- const token = await getToken();
801
- const response = await fetch(`${PAYMENT_API_BASE}/v3/payment/options`, {
802
- method: "POST",
803
- headers: {
804
- "Accept": "application/json, text/plain, */*",
805
- "Authorization": `Bearer ${token}`,
806
- "Content-Type": "application/json",
807
- "User-Agent": USER_AGENT,
808
- "Origin": "https://tgoyemek.com",
809
- "app-name": "TrendyolGo",
810
- "x-applicationid": "1",
811
- "x-channelid": "4",
812
- "x-storefrontid": "1",
813
- "x-features": "OPTIONAL_REBATE;MEAL_CART_ENABLED",
814
- "x-supported-payment-options": "MULTINET;SODEXO;EDENRED;ON_DELIVERY;SETCARD",
815
- "x-correlationid": randomUUID(),
816
- "pid": randomUUID(),
817
- "sid": randomUUID()
818
- },
819
- body: JSON.stringify({
820
- paymentType: "payWithCard",
821
- data: {
822
- savedCardId: cardId,
823
- binCode: binCode,
824
- installmentId: 0,
825
- reward: null,
826
- installmentPostponingSelected: false
827
- }
828
- })
829
- });
830
- if (!response.ok) {
831
- throw new Error(`Failed to select payment method: ${response.status} ${response.statusText}`);
832
- }
833
- }
834
- export async function placeOrder(cardId) {
835
- const token = await getToken();
836
- // First, get the saved cards to find the bin code for this card
837
- const cardsResponse = await getSavedCards();
838
- const card = cardsResponse.cards.find(c => c.cardId === cardId);
839
- if (!card) {
840
- return {
841
- success: false,
842
- message: `Card with ID ${cardId} not found. Use get_saved_cards to see available cards.`
843
- };
844
- }
845
- // Extract bin code from masked card number (first 6 digits + **)
846
- const binCode = card.maskedCardNumber.substring(0, 6) + "**";
847
- // IMPORTANT: Use the same session IDs across all payment-related calls
848
- // This is required for the payment system to track the transaction properly
849
- const correlationId = randomUUID();
850
- const pid = randomUUID();
851
- const sid = randomUUID();
852
- const paymentHeaders = {
853
- "Accept": "application/json, text/plain, */*",
854
- "Authorization": `Bearer ${token}`,
855
- "Content-Type": "application/json",
856
- "User-Agent": USER_AGENT,
857
- "Origin": "https://tgoyemek.com",
858
- "app-name": "TrendyolGo",
859
- "x-applicationid": "1",
860
- "x-channelid": "4",
861
- "x-storefrontid": "1",
862
- "x-features": "OPTIONAL_REBATE;MEAL_CART_ENABLED",
863
- "x-supported-payment-options": "MULTINET;SODEXO;EDENRED;ON_DELIVERY;SETCARD",
864
- "x-correlationid": correlationId,
865
- "pid": pid,
866
- "sid": sid
867
- };
868
- // Step 1: Initialize cart state in payment system
869
- const checkoutResponse = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/carts?cartContext=payment&limitPromoMbs=false`, { method: "GET", headers: paymentHeaders });
870
- if (!checkoutResponse.ok) {
871
- return {
872
- success: false,
873
- message: `Failed to initialize checkout: ${checkoutResponse.status} ${checkoutResponse.statusText}`
874
- };
875
- }
876
- // Step 2: Select payment method
877
- const optionsResponse = await fetch(`${PAYMENT_API_BASE}/v3/payment/options`, {
878
- method: "POST",
879
- headers: paymentHeaders,
880
- body: JSON.stringify({
881
- paymentType: "payWithCard",
882
- data: {
883
- savedCardId: cardId,
884
- binCode: binCode,
885
- installmentId: 0,
886
- reward: null,
887
- installmentPostponingSelected: false
888
- }
889
- })
890
- });
891
- if (!optionsResponse.ok) {
892
- return {
893
- success: false,
894
- message: `Failed to select payment method: ${optionsResponse.status} ${optionsResponse.statusText}`
895
- };
896
- }
897
- // Step 3: Place the order with 3D Secure
898
- const response = await fetch(`${PAYMENT_API_BASE}/v2/payment/pay`, {
899
- method: "POST",
900
- headers: paymentHeaders,
901
- body: JSON.stringify({
902
- customerSelectedThreeD: false,
903
- paymentOptions: [
904
- {
905
- name: "payWithCard",
906
- cardNo: "",
907
- customerSelectedThreeD: false
908
- }
909
- ],
910
- callbackUrl: "https://tgoyemek.com/odeme"
911
- })
912
- });
913
- if (!response.ok) {
914
- const errorText = await response.text();
915
- // Check for 3D Secure requirement in error response
916
- if (response.status === 400 || response.status === 403) {
917
- try {
918
- const errorData = JSON.parse(errorText);
919
- if (errorData.redirectUrl || errorData.requires3DSecure || errorData.threeDSecureUrl || errorData.htmlContent || errorData.json?.content) {
920
- return {
921
- success: false,
922
- requires3DSecure: true,
923
- redirectUrl: errorData.redirectUrl || errorData.threeDSecureUrl,
924
- htmlContent: errorData.htmlContent || errorData.json?.content,
925
- message: "3D Secure verification required. Complete payment in browser."
926
- };
927
- }
928
- }
929
- catch {
930
- // Not JSON, continue with generic error
931
- }
932
- }
933
- throw new Error(`Failed to place order: ${response.status} ${response.statusText}`);
934
- }
935
- const data = await response.json();
936
- // Check if 3D Secure HTML content is returned (successful 3D Secure initiation)
937
- if (data.json?.content) {
938
- // Extract redirect URL from HTML form if present
939
- const formMatch = data.json.content.match(/action="([^"]+)"/);
940
- const redirectUrl = formMatch ? formMatch[1] : undefined;
941
- return {
942
- success: false,
943
- requires3DSecure: true,
944
- redirectUrl,
945
- htmlContent: data.json.content,
946
- message: "3D Secure verification required. Complete payment in browser."
947
- };
948
- }
949
- // Check other 3D Secure indicators
950
- if (data.requires3DSecure || data.redirectUrl || data.threeDSecureUrl || data.htmlContent) {
951
- return {
952
- success: false,
953
- requires3DSecure: true,
954
- redirectUrl: data.redirectUrl || data.threeDSecureUrl,
955
- htmlContent: data.htmlContent,
956
- message: "3D Secure verification required. Complete payment in browser."
957
- };
958
- }
959
- return {
960
- success: true,
961
- orderId: data.orderId || data.orderNumber || data.id,
962
- message: "Order placed successfully!"
963
- };
964
- }
965
- export async function getOrders(page = 1) {
966
- const token = await getToken();
967
- const pageSize = 50;
968
- const params = new URLSearchParams({
969
- page: page.toString(),
970
- pageSize: pageSize.toString()
971
- });
972
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/orders?${params}`, {
973
- method: "GET",
974
- headers: {
975
- "Accept": "application/json, text/plain, */*",
976
- "Authorization": `Bearer ${token}`,
977
- "User-Agent": USER_AGENT,
978
- "Origin": "https://tgoyemek.com",
979
- "x-correlationid": randomUUID(),
980
- "pid": randomUUID(),
981
- "sid": randomUUID()
982
- }
983
- });
984
- if (!response.ok) {
985
- throw new Error(`Failed to fetch orders: ${response.status} ${response.statusText}`);
986
- }
987
- const data = await response.json();
988
- // Transform orders to simplified format
989
- const orders = (data.orders || []).map((o) => ({
990
- id: o.id,
991
- orderDate: o.orderDate ?? "",
992
- store: {
993
- id: o.store?.id ?? 0,
994
- name: o.store?.name ?? ""
995
- },
996
- status: {
997
- status: o.status?.status ?? "",
998
- statusText: o.status?.statusText ?? "",
999
- statusColor: o.status?.statusColor ?? ""
1000
- },
1001
- price: {
1002
- totalPrice: o.price?.totalPrice ?? 0,
1003
- totalPriceText: o.price?.totalPriceText ?? "",
1004
- refundedPrice: o.price?.refundedPrice ?? 0,
1005
- cancelledPrice: o.price?.cancelledPrice ?? 0,
1006
- totalDeliveryPrice: o.price?.totalDeliveryPrice ?? 0,
1007
- totalServicePrice: o.price?.totalServicePrice ?? 0
1008
- },
1009
- productSummary: o.product?.name ?? "",
1010
- products: (o.productList || []).map((p) => ({
1011
- productId: p.productId,
1012
- name: p.name,
1013
- imageUrl: p.imageUrl ?? ""
1014
- })),
1015
- isReady: o.isReady ?? false
1016
- }));
1017
- return {
1018
- orders,
1019
- pagination: {
1020
- currentPage: data.pagination?.currentPage ?? page,
1021
- pageSize: data.pagination?.pageSize ?? pageSize,
1022
- totalCount: data.pagination?.totalCount ?? 0,
1023
- hasNext: data.pagination?.hasNext ?? false
1024
- }
1025
- };
1026
- }
1027
- export async function getOrderDetail(orderId) {
1028
- const token = await getToken();
1029
- const params = new URLSearchParams({
1030
- orderId
1031
- });
1032
- const response = await fetch(`${API_BASE}/web-checkout-apicheckout-santral/orders/detail?${params}`, {
1033
- method: "GET",
1034
- headers: {
1035
- "Accept": "application/json, text/plain, */*",
1036
- "Authorization": `Bearer ${token}`,
1037
- "User-Agent": USER_AGENT,
1038
- "Origin": "https://tgoyemek.com",
1039
- "x-correlationid": randomUUID(),
1040
- "pid": randomUUID(),
1041
- "sid": randomUUID()
1042
- }
1043
- });
1044
- if (!response.ok) {
1045
- throw new Error(`Failed to fetch order detail: ${response.status} ${response.statusText}`);
1046
- }
1047
- const data = await response.json();
1048
- // Extract shipment info
1049
- const shipment = data.shipment;
1050
- const shipmentSummary = shipment?.summary;
1051
- const shipmentItem = shipment?.items?.[0];
1052
- // Extract status steps from shipment item state
1053
- const statusSteps = (shipmentItem?.state?.statuses || []).map((s) => ({
1054
- status: s.status ?? "",
1055
- statusText: s.statusText ?? ""
1056
- }));
1057
- // Extract products from shipment item
1058
- const products = (shipmentItem?.products || []).map((p) => ({
1059
- name: p.name ?? "",
1060
- imageUrl: p.imageUrl ?? "",
1061
- salePrice: p.salePrice ?? 0,
1062
- salePriceText: p.salePriceText ?? "",
1063
- quantity: p.quantity ?? 1,
1064
- description: p.description ?? ""
1065
- }));
1066
- // Extract delivery address
1067
- const addr = data.deliveryAddress;
1068
- // Extract price from summary
1069
- const summaryPrice = data.summary?.price;
1070
- return {
1071
- orderId: data.summary?.orderId ?? orderId,
1072
- orderNumber: data.summary?.orderNumber ?? "",
1073
- orderDate: data.summary?.orderDate ?? "",
1074
- customerNote: data.summary?.customerNote ?? "",
1075
- store: {
1076
- id: parseInt(shipmentSummary?.store?.id, 10) || 0,
1077
- name: shipmentSummary?.store?.name ?? ""
1078
- },
1079
- eta: shipmentSummary?.eta ?? "",
1080
- deliveredDate: shipmentSummary?.deliveredDate ?? "",
1081
- status: {
1082
- status: shipmentItem?.status?.status ?? "",
1083
- statusText: shipmentItem?.status?.statusText ?? "",
1084
- statusColor: shipmentItem?.status?.statusColor ?? ""
1085
- },
1086
- statusSteps,
1087
- products,
1088
- price: {
1089
- totalPrice: summaryPrice?.totalPrice ?? 0,
1090
- totalPriceText: summaryPrice?.totalPriceText ?? "",
1091
- refundedPrice: summaryPrice?.refundedPrice ?? 0,
1092
- cancelledPrice: summaryPrice?.cancelledPrice ?? 0,
1093
- totalDeliveryPrice: summaryPrice?.totalDeliveryPrice ?? 0,
1094
- totalServicePrice: summaryPrice?.totalServicePrice ?? 0
1095
- },
1096
- paymentDescription: data.paymentInfo?.paymentDescription ?? "",
1097
- deliveryAddress: {
1098
- name: addr?.name ?? "",
1099
- address: addr?.address ?? "",
1100
- districtCity: addr?.districtCity ?? "",
1101
- phoneNumber: addr?.phoneNumber ?? ""
1102
- }
1103
- };
1104
- }
1105
- export async function searchRestaurants(searchQuery, latitude, longitude, page = 1) {
1106
- const token = await getToken();
1107
- const pageSize = 50;
1108
- const params = new URLSearchParams({
1109
- searchQuery,
1110
- latitude,
1111
- longitude,
1112
- pageSize: pageSize.toString(),
1113
- page: page.toString()
1114
- });
1115
- const response = await fetch(`${API_BASE}/web-restaurant-apirestaurant-santral/restaurants/in/search?${params}`, {
1116
- method: "GET",
1117
- headers: {
1118
- "Accept": "application/json, text/plain, */*",
1119
- "Authorization": `Bearer ${token}`,
1120
- "User-Agent": USER_AGENT,
1121
- "Origin": "https://tgoyemek.com",
1122
- "x-correlationid": randomUUID(),
1123
- "pid": randomUUID(),
1124
- "sid": randomUUID()
1125
- }
1126
- });
1127
- if (!response.ok) {
1128
- throw new Error(`Failed to search restaurants: ${response.status} ${response.statusText}`);
1129
- }
1130
- const data = await response.json();
1131
- // Transform to simplified format for AI context efficiency
1132
- const restaurants = (data.restaurants || []).map((r) => {
1133
- const isClosed = r.isClosed ?? false;
1134
- return {
1135
- id: r.id,
1136
- name: r.name,
1137
- kitchen: r.kitchen ?? "",
1138
- rating: r.rating ?? 0,
1139
- ratingText: r.ratingText ?? "",
1140
- minBasketPrice: r.minBasketPrice ?? 0,
1141
- averageDeliveryInterval: r.averageDeliveryInterval ?? "",
1142
- distance: r.location?.distance ?? 0,
1143
- neighborhoodName: r.location?.neighborhoodName ?? "",
1144
- isClosed,
1145
- campaignText: r.campaignText,
1146
- products: (r.products || []).map((p) => ({
1147
- id: p.id,
1148
- name: p.name,
1149
- description: p.description,
1150
- price: p.price?.salePrice ?? p.price ?? 0,
1151
- imageUrl: p.imageUrl
1152
- })),
1153
- ...(isClosed && { warning: "This restaurant is currently closed. Do not proceed with ordering from this restaurant." })
1154
- };
1155
- });
1156
- return {
1157
- restaurants,
1158
- totalCount: data.restaurantCount ?? 0,
1159
- currentPage: page,
1160
- pageSize,
1161
- hasNextPage: !!data.links?.next?.href,
1162
- searchQuery: data.searchQuery ?? searchQuery
1163
- };
1164
- }