bookEvent.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { Context } from "grammy";
  2. import { CosmoeClient } from "../../client/cosmoe";
  3. export interface Env {
  4. BOT_INFO: string;
  5. BOT_TOKEN: string;
  6. COSMOE_CREDENTIALS: KVNamespace;
  7. COSMOE_STORAGE: KVNamespace;
  8. }
  9. export async function handleBookEvent(ctx: Context, env: Env): Promise<void> {
  10. try {
  11. if (!ctx.message || !ctx.message.text) {
  12. return;
  13. }
  14. const match = /^\/book_(\d+)_(\d+)$/.exec(ctx.message.text);
  15. if (match && match[1] && match[2]) {
  16. const eventId = match[1];
  17. const slotIndex = parseInt(match[2]);
  18. // Get user credentials from KV storage
  19. const telegramUserId = ctx.from?.id.toString();
  20. if (!telegramUserId) {
  21. await ctx.reply("Could not identify your Telegram user ID.");
  22. return;
  23. }
  24. const storedCredentials = await env.COSMOE_CREDENTIALS.get(telegramUserId);
  25. if (!storedCredentials) {
  26. await ctx.reply("You need to login first using /login username password before booking events.");
  27. return;
  28. }
  29. const credentials = JSON.parse(storedCredentials);
  30. // Initialize Cosmoe API client and set credentials
  31. const cosmoeClient = new CosmoeClient();
  32. cosmoeClient.setCredentials(credentials.user_id, credentials.token);
  33. // Fetch event details to get time slots
  34. const eventResult = await cosmoeClient.getEventDetail(eventId);
  35. if (eventResult.code !== 200) {
  36. await ctx.reply(`Event not found: ${eventResult.msg || 'Unknown error'}`);
  37. return;
  38. }
  39. const event = eventResult.data;
  40. // Sort slots by range for consistent ordering
  41. const sortedSlots = [...event.slots].sort((a, b) => a.range.localeCompare(b.range));
  42. if (slotIndex < 0 || slotIndex >= sortedSlots.length) {
  43. await ctx.reply(`Invalid slot number. Valid range is 0-${sortedSlots.length - 1}.`);
  44. return;
  45. }
  46. const selectedSlot = sortedSlots[slotIndex];
  47. if (selectedSlot.remaining <= 0) {
  48. await ctx.reply(`Sorry, the selected slot (${selectedSlot.range}) is fully booked. No spots left.`);
  49. return;
  50. }
  51. // Check for available coupons for this event
  52. let couponCode: string | undefined;
  53. try {
  54. const couponsResult = await cosmoeClient.getAvailableCoupons(eventId);
  55. if (couponsResult.code === 200 && couponsResult.data && couponsResult.data.length > 0) {
  56. // Use the first available coupon (you could implement logic to select the best one)
  57. const availableCoupon = couponsResult.data[0];
  58. couponCode = availableCoupon.code;
  59. console.log(`Using coupon ${couponCode} for event ${eventId}`);
  60. }
  61. } catch (error) {
  62. console.warn('Failed to fetch coupons, proceeding without coupon:', error);
  63. }
  64. // Attempt to book the event with coupon if available
  65. const bookingRequest: any = {
  66. event_id: eventId,
  67. time_slot: selectedSlot.range,
  68. };
  69. if (couponCode) {
  70. bookingRequest.coupon_code = couponCode;
  71. }
  72. const bookingResult = await cosmoeClient.bookEvent(bookingRequest);
  73. if (bookingResult.code === 200) {
  74. // Extract final price and booking ID from the response data if available
  75. const finalPrice = bookingResult.data?.final_price || selectedSlot.price;
  76. const bookingId = bookingResult.data?.id;
  77. let successMessage = `Successfully booked event ${event.name} for slot ${selectedSlot.range}!`;
  78. successMessage += `\nFinal Price: ¥${finalPrice}`;
  79. if (bookingId) {
  80. successMessage += `\nBooking ID: ${bookingId}`;
  81. }
  82. if (couponCode) {
  83. successMessage += ` \n\(Used coupon: ${couponCode}\)`;
  84. }
  85. await ctx.reply(successMessage);
  86. } else {
  87. let errorMessage = `Booking failed: ${bookingResult.msg || 'Unknown error'}`;
  88. if (couponCode) {
  89. // Retry without coupon in case the coupon caused the failure
  90. const retryResult = await cosmoeClient.bookEvent({
  91. event_id: eventId,
  92. time_slot: selectedSlot.range,
  93. });
  94. if (retryResult.code === 200) {
  95. await ctx.reply(`Successfully booked event ${event.name} for slot ${selectedSlot.range}! \(Original attempt failed with coupon, succeeded without coupon\)`);
  96. } else {
  97. await ctx.reply(errorMessage);
  98. }
  99. } else {
  100. await ctx.reply(errorMessage);
  101. }
  102. }
  103. }
  104. } catch (error) {
  105. console.error("Error handling booking request:", error);
  106. await ctx.reply("An error occurred while processing your booking request. Please try again.");
  107. }
  108. }