spree_cm_commissioner 2.13.3.pre.pre3 → 2.13.4.pre.pre1

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.
Files changed (260) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +0 -2
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +10 -8
  5. data/app/controllers/spree/admin/integrations_controller.rb +7 -5
  6. data/app/controllers/spree/admin/inventory_items_controller.rb +0 -12
  7. data/app/controllers/spree/admin/oauth_application_api_keys_controller.rb +118 -0
  8. data/app/controllers/spree/admin/oauth_applications_controller.rb +10 -0
  9. data/app/controllers/spree/api/v2/storefront/dynamic_field_options_controller.rb +12 -0
  10. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +0 -5
  11. data/app/controllers/spree/api/v2/tenant/trip_search_controller.rb +0 -5
  12. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +0 -3
  13. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +2 -6
  14. data/app/interactors/spree_cm_commissioner/user_acleda_bank_web_app_authenticator.rb +38 -0
  15. data/app/interactors/spree_cm_commissioner/user_wing_bank_web_app_authenticator.rb +39 -0
  16. data/app/models/concerns/spree_cm_commissioner/line_item_bundleable.rb +38 -0
  17. data/app/models/concerns/spree_cm_commissioner/oauth_application/mini_app.rb +72 -0
  18. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  19. data/app/models/spree_cm_commissioner/dynamic_field.rb +12 -1
  20. data/app/models/spree_cm_commissioner/dynamic_field_option.rb +11 -0
  21. data/app/models/spree_cm_commissioner/guest.rb +8 -0
  22. data/app/models/spree_cm_commissioner/integration.rb +0 -46
  23. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +10 -4
  24. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -18
  25. data/app/models/spree_cm_commissioner/line_item_decorator.rb +1 -0
  26. data/app/models/spree_cm_commissioner/mini_app_provider/acleda_bank.rb +28 -0
  27. data/app/models/spree_cm_commissioner/mini_app_provider/base.rb +219 -0
  28. data/app/models/spree_cm_commissioner/mini_app_provider/vattanac_bank.rb +6 -0
  29. data/app/models/spree_cm_commissioner/mini_app_provider/wing_bank.rb +30 -0
  30. data/app/models/spree_cm_commissioner/oauth_application_decorator.rb +7 -0
  31. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -2
  32. data/app/models/spree_cm_commissioner/place.rb +0 -2
  33. data/app/models/spree_cm_commissioner/product_dynamic_field.rb +9 -0
  34. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +1 -2
  35. data/app/models/spree_cm_commissioner/route.rb +0 -1
  36. data/app/models/spree_cm_commissioner/seat_layout.rb +0 -1
  37. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +1 -1
  38. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +1 -1
  39. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -4
  40. data/app/models/spree_cm_commissioner/trip.rb +1 -2
  41. data/app/models/spree_cm_commissioner/trip_stop.rb +0 -2
  42. data/app/models/spree_cm_commissioner/user_identity_provider.rb +1 -1
  43. data/app/models/spree_cm_commissioner/variant_decorator.rb +0 -5
  44. data/app/models/spree_cm_commissioner/vehicle.rb +0 -3
  45. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -1
  46. data/app/models/spree_cm_commissioner/vendor_decorator.rb +0 -17
  47. data/app/models/spree_cm_commissioner/vendor_place.rb +0 -2
  48. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +2 -1
  49. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +2 -1
  50. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +0 -11
  51. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +15 -14
  52. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +4 -7
  53. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +3 -6
  54. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +3 -0
  55. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +3 -1
  56. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +3 -1
  57. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +2 -1
  58. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +0 -4
  59. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +0 -4
  60. data/app/services/spree_cm_commissioner/mini_app_provider/api_key_history.rb +128 -0
  61. data/app/services/spree_cm_commissioner/mini_app_provider/ip_rules.rb +26 -0
  62. data/app/services/spree_cm_commissioner/mini_app_provider/session_gate.rb +79 -0
  63. data/app/services/spree_cm_commissioner/mini_app_provider/session_throttle.rb +66 -0
  64. data/app/services/spree_cm_commissioner/order_holds/hold.rb +0 -17
  65. data/app/services/spree_cm_commissioner/order_holds/release.rb +0 -17
  66. data/app/services/spree_cm_commissioner/payment_method_type_mapper.rb +3 -1
  67. data/app/services/spree_cm_commissioner/rate_limiter/result.rb +20 -0
  68. data/app/services/spree_cm_commissioner/rate_limiter.rb +62 -0
  69. data/app/services/spree_cm_commissioner/seat_layouts/update.rb +4 -0
  70. data/app/services/spree_cm_commissioner/seeds/user_usernames.rb +3 -1
  71. data/app/services/spree_cm_commissioner/ticket_transfers/eligibility_checker.rb +5 -6
  72. data/app/services/spree_cm_commissioner/ticket_transfers/preview_rule.rb +3 -5
  73. data/app/services/spree_cm_commissioner/transit_order/create.rb +8 -2
  74. data/app/services/spree_cm_commissioner/user_authenticator.rb +12 -2
  75. data/app/services/spree_cm_commissioner/users/backfill_device_tokens_count.rb +48 -0
  76. data/app/services/spree_cm_commissioner/wing_mini_app/config.rb +44 -0
  77. data/app/services/spree_cm_commissioner/wing_mini_app/find_or_create_user.rb +89 -0
  78. data/app/services/spree_cm_commissioner/wing_mini_app/profile_verifier.rb +65 -0
  79. data/app/views/spree/admin/guests/_table.html.erb +4 -0
  80. data/app/views/spree/admin/integrations/_form.html.erb +0 -16
  81. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  82. data/app/views/spree/admin/oauth_applications/_api_key_minted_modal.html.erb +71 -0
  83. data/app/views/spree/admin/oauth_applications/_api_key_modal.html.erb +68 -0
  84. data/app/views/spree/admin/oauth_applications/_api_key_restore_list.html.erb +40 -0
  85. data/app/views/spree/admin/oauth_applications/_api_key_restore_modal.html.erb +72 -0
  86. data/app/views/spree/admin/oauth_applications/_api_key_revoke_modal.html.erb +54 -0
  87. data/app/views/spree/admin/oauth_applications/_form.html.erb +152 -0
  88. data/app/views/spree/admin/oauth_applications/edit.html.erb +36 -0
  89. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +8 -2
  90. data/config/locales/en.yml +12 -2
  91. data/config/locales/km.yml +0 -2
  92. data/config/routes.rb +15 -25
  93. data/db/migrate/20260925000000_change_commission_rate_default_on_spree_vendors.rb +5 -0
  94. data/db/migrate/20260925090000_add_parent_id_and_relation_type_to_spree_line_items.rb +6 -0
  95. data/db/migrate/20260928070109_add_provider_and_api_key_to_spree_oauth_applications.rb +11 -0
  96. data/lib/spree_cm_commissioner/cached_inventory_item.rb +1 -7
  97. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +0 -17
  98. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +16 -2
  99. data/lib/spree_cm_commissioner/version.rb +1 -1
  100. data/lib/spree_cm_commissioner.rb +8 -1
  101. data/lib/tasks/backfill_device_tokens_count.rake +8 -0
  102. metadata +32 -160
  103. data/app/controllers/spree/api/switch_cm/base_controller.rb +0 -98
  104. data/app/controllers/spree/api/switch_cm/ota/avail_notifs_controller.rb +0 -17
  105. data/app/controllers/spree/api/switch_cm/ota/base_controller.rb +0 -27
  106. data/app/controllers/spree/api/switch_cm/ota/product_notifs_controller.rb +0 -44
  107. data/app/controllers/spree/api/switch_cm/ota/pushes_controller.rb +0 -46
  108. data/app/controllers/spree/api/switch_cm/ota/rate_amount_notifs_controller.rb +0 -17
  109. data/app/controllers/spree/api/switch_cm/ota/res_modify_notifs_controller.rb +0 -31
  110. data/app/controllers/spree/api/switch_cm/ota/res_notifs_controller.rb +0 -29
  111. data/app/controllers/spree/api/switch_cm/ota/reservation_streams_controller.rb +0 -83
  112. data/app/controllers/spree/api/switch_cm/tokens_controller.rb +0 -68
  113. data/app/controllers/spree/api/switch_cm/xml/availabilities_controller.rb +0 -39
  114. data/app/controllers/spree/api/switch_cm/xml/base_controller.rb +0 -37
  115. data/app/controllers/spree/api/switch_cm/xml/reservation_summaries_controller.rb +0 -28
  116. data/app/controllers/spree/api/switch_cm/xml/reservations_controller.rb +0 -56
  117. data/app/controllers/spree/api/switch_cm/xml/room_rate_availabilities_controller.rb +0 -56
  118. data/app/controllers/spree/api/switch_cm/xml/room_rates_controller.rb +0 -32
  119. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +0 -66
  120. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +0 -29
  121. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +0 -139
  122. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +0 -9
  123. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +0 -24
  124. data/app/models/spree_cm_commissioner/audit_event.rb +0 -11
  125. data/app/models/spree_cm_commissioner/currency_rate.rb +0 -15
  126. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +0 -64
  127. data/app/models/spree_cm_commissioner/integrations/larryta.rb +0 -81
  128. data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +0 -86
  129. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +0 -82
  130. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +0 -10
  131. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +0 -10
  132. data/app/services/spree_cm_commissioner/audit_logger.rb +0 -59
  133. data/app/services/spree_cm_commissioner/currency_converter/create.rb +0 -27
  134. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +0 -99
  135. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +0 -41
  136. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +0 -230
  137. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +0 -147
  138. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +0 -52
  139. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +0 -91
  140. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +0 -188
  141. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +0 -79
  142. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +0 -354
  143. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +0 -103
  144. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +0 -93
  145. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +0 -70
  146. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +0 -121
  147. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +0 -128
  148. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +0 -412
  149. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +0 -59
  150. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +0 -224
  151. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +0 -769
  152. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +0 -69
  153. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +0 -92
  154. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +0 -19
  155. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +0 -40
  156. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +0 -148
  157. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +0 -55
  158. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +0 -85
  159. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +0 -128
  160. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +0 -386
  161. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +0 -84
  162. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +0 -237
  163. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +0 -48
  164. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +0 -22
  165. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +0 -116
  166. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +0 -63
  167. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +0 -314
  168. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +0 -74
  169. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +0 -53
  170. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +0 -106
  171. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +0 -143
  172. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +0 -90
  173. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +0 -143
  174. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +0 -325
  175. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +0 -72
  176. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +0 -142
  177. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +0 -255
  178. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +0 -55
  179. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +0 -171
  180. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +0 -397
  181. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +0 -63
  182. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +0 -79
  183. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +0 -39
  184. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +0 -113
  185. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +0 -43
  186. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +0 -236
  187. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +0 -71
  188. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +0 -48
  189. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +0 -150
  190. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +0 -31
  191. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +0 -49
  192. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +0 -211
  193. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +0 -17
  194. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +0 -79
  195. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +0 -58
  196. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accept_push.rb +0 -103
  197. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/accommodation_lock.rb +0 -43
  198. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/apply_updates.rb +0 -121
  199. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/availability_snapshot.rb +0 -88
  200. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/inventory_update.rb +0 -26
  201. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/pushed_nights.rb +0 -81
  202. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/unseen_room_nights.rb +0 -41
  203. data/app/services/spree_cm_commissioner/integrations/switch_cm/inventory/validate_updates.rb +0 -138
  204. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/availability_parser.rb +0 -60
  205. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/base_parser.rb +0 -60
  206. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/price_parser.rb +0 -46
  207. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/push_response.rb +0 -38
  208. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/reservation_serializer.rb +0 -99
  209. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/response_builder.rb +0 -52
  210. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/acknowledge.rb +0 -45
  211. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/feed.rb +0 -145
  212. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/mark_seen.rb +0 -55
  213. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservations/reservation.rb +0 -120
  214. data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +0 -56
  215. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_manager.rb +0 -16
  216. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/full_sync_strategy.rb +0 -39
  217. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/incremental_sync_strategy.rb +0 -51
  218. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/availability_parser.rb +0 -103
  219. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/push_response.rb +0 -45
  220. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/reservation_serializer.rb +0 -74
  221. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/response_builder.rb +0 -19
  222. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +0 -170
  223. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +0 -59
  224. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +0 -48
  225. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +0 -92
  226. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +0 -230
  227. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +0 -270
  228. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +0 -96
  229. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +0 -76
  230. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +0 -69
  231. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +0 -110
  232. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +0 -207
  233. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +0 -43
  234. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +0 -192
  235. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +0 -377
  236. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +0 -56
  237. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +0 -51
  238. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +0 -32
  239. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +0 -48
  240. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +0 -46
  241. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +0 -53
  242. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +0 -43
  243. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +0 -37
  244. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +0 -138
  245. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +0 -48
  246. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +0 -79
  247. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +0 -17
  248. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +0 -87
  249. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +0 -53
  250. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +0 -48
  251. data/app/services/spree_cm_commissioner/place_resolver.rb +0 -95
  252. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +0 -47
  253. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +0 -58
  254. data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +0 -53
  255. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +0 -75
  256. data/db/migrate/20260327143200_create_cm_audit_events.rb +0 -21
  257. data/db/migrate/20260410045815_create_cm_currency_rates.rb +0 -23
  258. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +0 -6
  259. data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +0 -5
  260. data/db/migrate/20260928000001_add_closed_to_cm_inventory_items.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2ffd7af989f00931883cac1fc7aa3ff4173458f57a42abd9294ecba2687c2bb
4
- data.tar.gz: d2ee7a2a5d558f018075eded5ef178d294a61afc6e53b5d77018c3ef3a239c04
3
+ metadata.gz: 046dd1ce98770679145a8be935a9effb826b78c3e5ca63a1fa192f63da587687
4
+ data.tar.gz: 5fcbc34dea16ab68471e12f627cdb8eef28c3fe93d92d071b0dee39e032fad6a
5
5
  SHA512:
6
- metadata.gz: db45749501e065ded53bef1baa37ebbbca96badd6384ac47505f72ed08dd45c0040e6ea22f50dfa4454ea32a3059d63aa7f9ce3e80fe4b97f99d3dca06e1f1ea
7
- data.tar.gz: fc0b52d82222015696750733ecc702ef57e425256ca4e460644f21fe48369251e01e41adee33848e9d78be225e3342d845db8c4df93e973c70a9a360c21e72cb
6
+ metadata.gz: e312bbb74e20d80b384b980ef0e1c13926946ffd6f1a7c9cdeec0a6ce32f68e254e9b033a4e256d18db5856b9bdba1ce33a93465b8feaf8d987f88af611202d9
7
+ data.tar.gz: 1a80c7bc586ccd24e77370692626d3da1d3c4418e04da0ec95e156f8c52f91121e17796265ef1aba2ac18579d70e734356609b1ff77e4ecf6b9871e4a04e5259
data/.env.example CHANGED
@@ -50,7 +50,6 @@ DISTANCE_SIGNING_KEY=hei********************VY
50
50
  # Cache durations (in seconds) for different content types
51
51
  # See: app/controllers/concerns/spree_cm_commissioner/content_cachable.rb
52
52
  CACHE_AUTO_INVALIDATION_DISABLED="" # Set to yes to disable CDN cache invalidation entirely (kill switch)
53
- CACHE_CDN_MAX_AGE=86400 # CDN/server-side cache duration for all responses
54
53
  CACHE_STATIC_MAX_AGE=86400 # Static content (countries, provinces, seat layouts, CMS pages) - 1 day
55
54
  CACHE_SEMI_STATIC_MAX_AGE=3600 # Semi-static content (menus, homepage backgrounds, routes) - 1 hour
56
55
  CACHE_MODERATE_MAX_AGE=1800 # Moderate freshness (products, events, vendors) - 30 minutes
@@ -87,4 +86,3 @@ PAYMENT_INCOMPLETE_NOTIFICATION_DELAY_IN_MINUTES=5 # Delay before notifying a c
87
86
 
88
87
  # See: app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb
89
88
  ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED="no" # Set to yes to enable the order integrity Telegram alert job (off by default to avoid flooding Telegram rate limits during traffic spikes)
90
-
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.13.3.pre.pre3)
37
+ spree_cm_commissioner (2.13.4.pre.pre1)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -24,7 +24,7 @@ module SpreeCmCommissioner
24
24
  MenusController
25
25
  HomepageBackgroundController
26
26
  HomepageDataController
27
- PopularRoutesController
27
+ PopularRoutePlacesController
28
28
  RoutePlacesController
29
29
  ShowsController
30
30
  EpisodesController
@@ -46,7 +46,6 @@ module SpreeCmCommissioner
46
46
  AccommodationsController
47
47
  VendorsController
48
48
  VendorPhotosController
49
- DynamicFieldsController
50
49
  ].freeze
51
50
 
52
51
  # Priority 4: High Freshness (5 minutes)
@@ -54,12 +53,9 @@ module SpreeCmCommissioner
54
53
  TripsController
55
54
  TripSearchController
56
55
  VotingContestantsController
56
+ DynamicFieldsController
57
57
  ].freeze
58
58
 
59
- def shared_max_age
60
- ENV.fetch('CACHE_CDN_MAX_AGE', 1.day.to_i).to_i
61
- end
62
-
63
59
  # Override This Method based on your UseCase
64
60
  # Or let it auto-detect based on controller name
65
61
  def client_max_age
@@ -78,12 +74,18 @@ module SpreeCmCommissioner
78
74
  end
79
75
  end
80
76
 
77
+ # Defaults to the client TTL so a tier's max-age bounds CDN staleness too: most endpoints
78
+ # (e.g. trips, trip search) have no invalidation on change, and an unlisted controller
79
+ # (max-age 0) must not be CDN-cached at all.
80
+ def shared_max_age
81
+ client_max_age
82
+ end
83
+
81
84
  def set_cache_control_for_cdn
82
85
  return if response.committed?
83
86
  return if response.status != 200
84
87
 
85
- # max-age: browser/client cache, s-maxage: CDN/server cache
86
- # Allow client caching for mobile apps while keeping CDN cache longer
88
+ # max-age: browser/client cache, s-maxage: CDN cache
87
89
  response.headers['Cache-Control'] = "public, max-age=#{client_max_age}, s-maxage=#{shared_max_age}"
88
90
 
89
91
  # Expires: Absolute timestamp when cache becomes stale (HTTP/1.0 fallback)
@@ -47,7 +47,6 @@ module Spree
47
47
  :full_sync_interval_hours,
48
48
  :tenant_id,
49
49
  :vendor_id,
50
- :oauth_application_id,
51
50
  public_metadata: {},
52
51
  private_metadata: {}
53
52
  )
@@ -63,7 +62,6 @@ module Spree
63
62
  @conflict_strategy_options ||= enum_to_options(SpreeCmCommissioner::Integration.conflict_strategies)
64
63
  @tenants ||= SpreeCmCommissioner::Tenant.select(:id, :name).order(:name)
65
64
  @vendors ||= Spree::Vendor.select(:id, :name).order(:name)
66
- @oauth_applications ||= Spree::OauthApplication.select(:id, :name).order(:name)
67
65
  end
68
66
 
69
67
  def enum_to_options(enum_hash)
@@ -71,10 +69,14 @@ module Spree
71
69
  end
72
70
 
73
71
  def integration_type_options
74
- types = SpreeCmCommissioner::Integration.types
75
- return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if types.blank?
72
+ files = Dir[SpreeCmCommissioner::Engine.root.join('app/models/spree_cm_commissioner/integrations/*.rb')]
76
73
 
77
- types.map { |class_name| [class_name, class_name] }
74
+ return [[Spree.t(:type), 'SpreeCmCommissioner::Integration']] if files.blank?
75
+
76
+ files.map do |path|
77
+ class_name = "SpreeCmCommissioner::Integrations::#{File.basename(path, '.rb').camelize}"
78
+ [class_name, class_name]
79
+ end.sort_by(&:first)
78
80
  end
79
81
  end
80
82
  end
@@ -2,7 +2,6 @@ module Spree
2
2
  module Admin
3
3
  class InventoryItemsController < StockManagementsController
4
4
  before_action :load_parent
5
- before_action :reject_external_inventory_edits, only: %i[bulk_update_prices bulk_update_stocks]
6
5
 
7
6
  # GET /products/:slug/inventory_items/prices?inventory_item_ids=1,2,3
8
7
  def prices
@@ -79,17 +78,6 @@ module Spree
79
78
 
80
79
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
81
80
  end
82
-
83
- private
84
-
85
- # The partner owns these per-night stocks and prices; its next update would silently overwrite
86
- # an edit made here.
87
- def reject_external_inventory_edits
88
- return unless @product.variants.includes(vendor: :integration).any?(&:inventory_external_wins?)
89
-
90
- flash[:error] = Spree.t('admin.inventory_external_wins', name: @product.vendor.integration.name)
91
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
92
- end
93
81
  end
94
82
  end
95
83
  end
@@ -0,0 +1,118 @@
1
+ module Spree
2
+ module Admin
3
+ # A mini app's API key as its own resource: POST mints one, PATCH restores a previous one, DELETE
4
+ # revokes it. Each asks for nothing else, so no ordinary save of the application can reach the key.
5
+ class OauthApplicationApiKeysController < Spree::Admin::BaseController
6
+ before_action :load_oauth_application
7
+
8
+ # A minted key is one the admin has no other copy of, so it is handed to the page this redirects
9
+ # to and shown there once. Stashed only if the save went through — a key that never reached the
10
+ # row is not one to hand out.
11
+ def create
12
+ minted = change_api_key(:saved) { save_api_key(nil) }
13
+
14
+ session[:minted_api_key] = @oauth_application.mini_app.api_key if minted
15
+ end
16
+
17
+ # Puts back a key this application used to hold, named by the version that recorded it. The value
18
+ # never travels through the request — the admin picks one and the plaintext is read out of history
19
+ # here, so no key can be typed in and nothing weak can get in.
20
+ def update
21
+ key = api_key_history.key_for(params[:version_id])
22
+
23
+ if key.blank?
24
+ flash[:error] = I18n.t('mini_apps.api_key.not_restorable')
25
+ return redirect_to_application
26
+ end
27
+
28
+ change_api_key(:restored) { save_api_key(key, source: 'restored') }
29
+ end
30
+
31
+ # Leaves the mini app with no key at all, rather than handing the partner a working one they never
32
+ # asked for — what a leak calls for. #create puts one back.
33
+ def destroy
34
+ mini_app = @oauth_application.mini_app
35
+
36
+ # Said plainly rather than reporting a revoke that removed nothing.
37
+ if mini_app&.supports_api_key? && mini_app.api_key.blank?
38
+ flash[:error] = I18n.t('mini_apps.api_key.nothing_to_revoke')
39
+ return redirect_to_application
40
+ end
41
+
42
+ change_api_key(:revoked) do
43
+ mini_app.revoke_api_key!
44
+ @oauth_application.save
45
+ end
46
+ end
47
+
48
+ # The dialog's contents, fetched when it is opened. Reading history costs a scan of the
49
+ # application's versions, which is not worth doing on every render of the edit page.
50
+ def history
51
+ render partial: 'spree/admin/oauth_applications/api_key_restore_list',
52
+ locals: { application: @oauth_application, entries: api_key_history.entries },
53
+ layout: false
54
+ end
55
+
56
+ private
57
+
58
+ # Authorized as an edit of the application, not as create/destroy of a key. Spelled out because
59
+ # #model_class only tells BaseController the class, never this row.
60
+ def load_oauth_application
61
+ @oauth_application = Spree::OauthApplication.find(params[:oauth_application_id])
62
+
63
+ authorize! :update, @oauth_application
64
+ end
65
+
66
+ def model_class
67
+ Spree::OauthApplication
68
+ end
69
+
70
+ # The gate every key change passes. Replacing, restoring or clearing a live key changes what the
71
+ # partner has to send, so it costs the application's name typed back — checked here because a
72
+ # hand-made request walks past the dialog. A mini app with no key yet is asked for nothing.
73
+ #
74
+ # Returns whether this request changed the key. A `flash[:success]` check would also see the
75
+ # previous request's flash, and #create would reveal a key it never minted.
76
+ def change_api_key(success)
77
+ mini_app = @oauth_application.mini_app
78
+ changed = false
79
+
80
+ if mini_app.nil? || !mini_app.supports_api_key?
81
+ flash[:error] = I18n.t('mini_apps.api_key.unsupported')
82
+ elsif confirmation_mismatched?(mini_app)
83
+ flash[:error] = I18n.t('mini_apps.api_key.confirmation_mismatched')
84
+ elsif yield
85
+ flash[:success] = I18n.t("mini_apps.api_key.#{success}")
86
+ changed = true
87
+ else
88
+ # The key's own validations still apply — a restored one another application has since taken
89
+ # lands here.
90
+ flash[:error] = @oauth_application.errors.full_messages.to_sentence
91
+ end
92
+
93
+ redirect_to_application
94
+
95
+ changed
96
+ end
97
+
98
+ def save_api_key(value, source: nil)
99
+ @oauth_application.mini_app.replace_api_key!(value, actor: try_spree_current_user, source: source)
100
+ @oauth_application.save
101
+ end
102
+
103
+ def api_key_history
104
+ SpreeCmCommissioner::MiniAppProvider::ApiKeyHistory.new(@oauth_application)
105
+ end
106
+
107
+ def confirmation_mismatched?(mini_app)
108
+ return false if mini_app.api_key.blank?
109
+
110
+ params[:api_key_confirm].to_s.strip != @oauth_application.name
111
+ end
112
+
113
+ def redirect_to_application
114
+ redirect_to spree.edit_admin_oauth_application_path(@oauth_application)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -5,8 +5,18 @@ module Spree
5
5
  # Edit forms allow users to modify scopes without forced default
6
6
  before_action :set_default_scopes, only: [:new]
7
7
 
8
+ # Declared after ResourceController's own callbacks so @object is loaded by the time it runs.
9
+ before_action :take_minted_api_key, only: [:edit]
10
+
8
11
  private
9
12
 
13
+ # Written by OauthApplicationApiKeysController#create and read here, because this is the page that
14
+ # shows it. The session rather than the flash, because the admin layout renders whichever flash
15
+ # entry happens to be first. Deleted as it is read, so it is on screen for exactly one render.
16
+ def take_minted_api_key
17
+ @minted_api_key = session.delete(:minted_api_key)
18
+ end
19
+
10
20
  def create_turbo_stream_enabled?
11
21
  true
12
22
  end
@@ -3,6 +3,8 @@ module Spree
3
3
  module V2
4
4
  module Storefront
5
5
  class DynamicFieldOptionsController < ::Spree::Api::V2::ResourceController
6
+ before_action :ensure_dynamic_field_enabled_for_product, if: -> { params[:product_id].present? }
7
+
6
8
  # override
7
9
  def collection
8
10
  return @collection if defined?(@collection)
@@ -17,6 +19,16 @@ module Spree
17
19
  @collection
18
20
  end
19
21
 
22
+ private
23
+
24
+ # Only the product-scoped route checks this; the legacy flat route serves any field by id.
25
+ def ensure_dynamic_field_enabled_for_product
26
+ SpreeCmCommissioner::ProductDynamicField.find_by!(
27
+ product_id: params[:product_id],
28
+ dynamic_field_id: params[:dynamic_field_id]
29
+ )
30
+ end
31
+
20
32
  # override
21
33
  def collection_serializer
22
34
  SpreeCmCommissioner::V2::Storefront::DynamicFieldOptionSerializer
@@ -25,11 +25,6 @@ module Spree
25
25
 
26
26
  private
27
27
 
28
- # override from ContentCacheable
29
- def max_age
30
- CACHE_EXPIRES_IN.to_i
31
- end
32
-
33
28
  # override
34
29
  def collection_cache_key
35
30
  cache_key_parts = [
@@ -25,11 +25,6 @@ module Spree
25
25
 
26
26
  private
27
27
 
28
- # override from ContentCacheable
29
- def max_age
30
- CACHE_EXPIRES_IN.to_i
31
- end
32
-
33
28
  # override
34
29
  def collection_cache_key
35
30
  cache_key_parts = [
@@ -5,9 +5,6 @@ module SpreeCmCommissioner
5
5
  module CheckoutControllerDecorator
6
6
  def self.prepended(base)
7
7
  base.include SpreeCmCommissioner::OrderConcern
8
- base.include SpreeCmCommissioner::TurnstileProtectable
9
-
10
- base.before_action -> { verify_turnstile! }, only: %i[create_payment]
11
8
  # Sets Current.load_test_bypass from the X-Request-Verify secret so
12
9
  # InventoryHolds::ValidateLimits can skip anti-abuse checks for trusted load tests.
13
10
  base.include SpreeCmCommissioner::LoadTestBypassable
@@ -200,14 +200,10 @@ module SpreeCmCommissioner
200
200
  grouped.map { |nationality_group, guests| customer_type_summary(nationality_group, guests) }.join(', ')
201
201
  end
202
202
 
203
- # e.g. "Local (Adult x1, Child x1)". `cm_saved_guests.age_group` is declared `null: false` in
204
- # the migration, but a dev/staging DB whose column predates that migration (added via
205
- # `add_column ... if_not_exists: true`, which silently skips the constraint if the column
206
- # already exists) can still carry real NULL rows - group those under "Unknown" rather than
207
- # crashing the whole order-completion Telegram notification on nil.humanize.
203
+ # e.g. "Local (Adult x1, Child x1)". age_group is NOT NULL, so it is always present.
208
204
  def customer_type_summary(nationality_group, guests)
209
205
  age_breakdown = guests
210
- .group_by { |sg| sg.age_group&.humanize || 'Unknown' }
206
+ .group_by { |sg| sg.age_group.humanize }
211
207
  .map { |age_group, age_guests| "#{age_group} x#{age_guests.count}" }
212
208
  .join(', ')
213
209
 
@@ -0,0 +1,38 @@
1
+ module SpreeCmCommissioner
2
+ # Authenticates an ACLEDA Mini App session_key (a JWT minted by
3
+ # Vpago::AcledaMiniApp::Session::Create, signed with the user's secure_token).
4
+ #
5
+ # Unlike the Vattanac authenticator, this enforces the JWT signature: a token
6
+ # must be signed with the user's secure_token or it is rejected.
7
+ class UserAcledaBankWebAppAuthenticator < BaseInteractor
8
+ delegate :session_key, to: :context
9
+
10
+ def call
11
+ return context.fail!(message: 'Invalid token') unless payload
12
+ return context.fail!(message: 'User not found') unless user
13
+ return context.fail!(message: 'Invalid token signature') unless valid_signature?
14
+
15
+ context.user = user
16
+ rescue JWT::DecodeError => e
17
+ context.fail!(message: "Decode error: #{e.message}")
18
+ end
19
+
20
+ private
21
+
22
+ # Unverified decode, only to read user_id. The signature is enforced
23
+ # separately in #valid_signature?.
24
+ def payload
25
+ @payload ||= SpreeCmCommissioner::UserSessionJwtToken.decode(session_key)
26
+ end
27
+
28
+ def user
29
+ @user ||= Spree::User.find_by(id: payload['user_id']) if payload
30
+ end
31
+
32
+ # UserSessionJwtToken.decode returns nil when the signature does not verify
33
+ # against the user's secure_token, so a forged/tampered token fails here.
34
+ def valid_signature?
35
+ SpreeCmCommissioner::UserSessionJwtToken.decode(session_key, user.secure_token).present?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module SpreeCmCommissioner
2
+ # Authenticates a Wing Mini App session_key (a JWT minted by
3
+ # Vpago::WingMiniApp::Session::Create, signed with the user's secure_token).
4
+ #
5
+ # Same shape as UserAcledaBankWebAppAuthenticator: the JWT signature is enforced
6
+ # here — a token must be signed with the user's secure_token or it is rejected.
7
+ class UserWingBankWebAppAuthenticator < BaseInteractor
8
+ delegate :session_key, to: :context
9
+
10
+ def call
11
+ return context.fail!(message: 'Invalid token') unless payload
12
+ return context.fail!(message: 'User not found') unless user
13
+ return context.fail!(message: 'Invalid token signature') unless valid_signature?
14
+
15
+ context.user = user
16
+ reject_unauthenticatable_user!(context.user)
17
+ rescue JWT::DecodeError => e
18
+ context.fail!(message: "Decode error: #{e.message}")
19
+ end
20
+
21
+ private
22
+
23
+ # Unverified decode, only to read user_id. The signature is enforced
24
+ # separately in #valid_signature?.
25
+ def payload
26
+ @payload ||= SpreeCmCommissioner::UserSessionJwtToken.decode(session_key)
27
+ end
28
+
29
+ def user
30
+ @user ||= Spree::User.find_by(id: payload['user_id']) if payload
31
+ end
32
+
33
+ # UserSessionJwtToken.decode returns nil when the signature does not verify
34
+ # against the user's secure_token, so a forged/tampered token fails here.
35
+ def valid_signature?
36
+ SpreeCmCommissioner::UserSessionJwtToken.decode(session_key, user.secure_token).present?
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ module SpreeCmCommissioner
2
+ # Groups add-on line items (sold via a ProductRelation) under the line item they were bought with.
3
+ #
4
+ # Grouping is display-only: each child keeps its own price, quantity and adjustments, since
5
+ # promotions, tax, service fees, payouts, refunds and open-dated redemption all work per line item.
6
+ # `relation_type` is a snapshot of the ProductRelation type at purchase, so later edits to the
7
+ # relation don't change what the order sold.
8
+ module LineItemBundleable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ belongs_to :parent, class_name: 'Spree::LineItem', optional: true, inverse_of: :children
13
+ has_many :children, class_name: 'Spree::LineItem', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
14
+
15
+ enum relation_type: SpreeCmCommissioner::ProductRelationType::PRODUCT_RELATION_TYPES.each_with_index.to_h { |type, index| [type, index] },
16
+ _prefix: :relation
17
+
18
+ scope :top_level, -> { where(parent_id: nil) }
19
+
20
+ money_methods :bundle_amount
21
+
22
+ validates :relation_type, presence: true, if: -> { parent_id.present? || parent.present? }
23
+ validates :relation_type, absence: true, if: -> { parent_id.blank? && parent.blank? }
24
+ validate :ensure_valid_parent, if: -> { parent.present? }
25
+ end
26
+
27
+ def bundle_amount
28
+ amount + children.sum(&:amount)
29
+ end
30
+
31
+ private
32
+
33
+ def ensure_valid_parent
34
+ errors.add(:parent, :invalid) if parent.parent_id.present? || parent == self
35
+ errors.add(:parent, :invalid) if parent.order_id.present? && order_id.present? && parent.order_id != order_id
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ module SpreeCmCommissioner
2
+ module OauthApplication
3
+ # Lets a Spree::OauthApplication stand for a mini app — the storefront a partner embeds in its
4
+ # own banking app. `provider` names which one; its settings live on a MiniAppProvider class.
5
+ module MiniApp
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Deterministic so `find_by` and the unique index keep working: calls are looked up by key.
10
+ encrypts :api_key, deterministic: true
11
+
12
+ # '' encrypts to a non-empty ciphertext, which the partial index cannot tell from a real key.
13
+ before_validation { self.api_key = nil if api_key.blank? }
14
+
15
+ # Unique: a keyless call is traced back by provider alone.
16
+ validates :provider,
17
+ inclusion: { in: -> (_application) { MiniAppProvider::Base.providers.map(&:provider) } },
18
+ uniqueness: true,
19
+ allow_blank: true
20
+
21
+ # `is`, not a minimum: a short or padded key saves fine, then fails every call the partner makes.
22
+ validates :api_key, uniqueness: true, allow_blank: true
23
+ validates :api_key, length: { is: MiniAppProvider::Base::API_KEY_LENGTH },
24
+ format: { without: /\s/, message: 'cannot contain spaces' }, # rubocop:disable Rails/I18nLocaleTexts
25
+ allow_blank: true
26
+
27
+ # Rules that do not parse stop matching silently and lock the mini app out.
28
+ validate do
29
+ mini_app&.invalid_ip_rules&.each do |name|
30
+ errors.add(:base, I18n.t('mini_apps.ip_rules.invalid', name: name.to_s.humanize))
31
+ end
32
+ end
33
+ end
34
+
35
+ class_methods do
36
+ # Authenticated lookup. The provider must match the row the key found, or one mini app's key
37
+ # would open another's flow. `api_key:` has no default: a missing header fails loudly here
38
+ # instead of falling through to #keyless_mini_app_for, where nothing is checked.
39
+ def mini_app_for(provider, api_key:)
40
+ return nil if api_key.blank?
41
+
42
+ application = find_by(api_key: api_key)
43
+ return nil unless application && application.provider.to_s == provider.to_s
44
+
45
+ mini_app = application.mini_app
46
+
47
+ mini_app if mini_app&.supports_api_key?
48
+ end
49
+
50
+ # For mini apps with no key to send — the partner only opens a WebView, so the customer's
51
+ # browser arrives, not the partner's server. Refuses any provider that does have a key, so
52
+ # nothing reaches a key-using mini app without it.
53
+ def keyless_mini_app_for(provider)
54
+ mini_app = find_by(provider: provider.to_s)&.mini_app
55
+
56
+ mini_app unless mini_app.nil? || mini_app.supports_api_key?
57
+ end
58
+ end
59
+
60
+ def mini_app
61
+ @mini_app ||= MiniAppProvider::Base.for(self)
62
+ end
63
+
64
+ # Preferences belonging to other mini apps are ignored rather than written to `settings`.
65
+ def mini_app_attributes=(attributes)
66
+ attributes.each do |name, value|
67
+ mini_app.public_send("#{name}=", value) if mini_app.respond_to?("#{name}=")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  module VehicleKind
3
3
  extend ActiveSupport::Concern
4
4
 
5
- VEHICLE_KINDS = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus ferry boat train].freeze
5
+ VEHICLE_KINDS = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus ferry train].freeze
6
6
 
7
7
  included do
8
8
  enum kind: VEHICLE_KINDS if table_exists? && column_names.include?('kind')
@@ -4,7 +4,7 @@ module SpreeCmCommissioner
4
4
 
5
5
  belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
6
6
 
7
- has_many :dynamic_field_options, class_name: 'SpreeCmCommissioner::DynamicFieldOption', dependent: :destroy
7
+ has_many :dynamic_field_options, -> { order(:position) }, class_name: 'SpreeCmCommissioner::DynamicFieldOption', dependent: :destroy
8
8
  has_many :guest_dynamic_fields, class_name: 'SpreeCmCommissioner::GuestDynamicField', dependent: :destroy
9
9
  has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
10
10
 
@@ -19,6 +19,7 @@ module SpreeCmCommissioner
19
19
  validate :validate_data_fill_stage_phase
20
20
 
21
21
  after_commit :enqueue_data_fill_stage_maintenance_for_products, on: %i[update], if: :saved_change_to_data_fill_stage?
22
+ after_commit :enqueue_cache_invalidation_for_products, on: %i[update]
22
23
 
23
24
  scope :with_dynamic_field_options, -> { includes(dynamic_field_options: :dynamic_field) }
24
25
 
@@ -57,6 +58,16 @@ module SpreeCmCommissioner
57
58
  end
58
59
  end
59
60
 
61
+ # Destroy isn't handled here: by after_commit the join rows are gone, and each
62
+ # ProductDynamicField destroyed with this field enqueues its own product.
63
+ def enqueue_cache_invalidation_for_products
64
+ product_dynamic_fields.distinct.pluck(:product_id).each do |product_id|
65
+ SpreeCmCommissioner::MaintenanceTasks::CacheInvalidation
66
+ .pending
67
+ .create_or_find_by(maintainable_type: 'Spree::Product', maintainable_id: product_id)
68
+ end
69
+ end
70
+
60
71
  def validate_data_fill_stage_phase
61
72
  return if data_fill_stage.blank?
62
73
 
@@ -11,8 +11,19 @@ module SpreeCmCommissioner
11
11
  validates :status, presence: true
12
12
  validate :parent_field_must_be_selection_type
13
13
 
14
+ after_commit :enqueue_cache_invalidation_for_products
15
+
14
16
  private
15
17
 
18
+ # Options are embedded in the storefront dynamic_fields response of every product using the parent field.
19
+ def enqueue_cache_invalidation_for_products
20
+ SpreeCmCommissioner::ProductDynamicField.where(dynamic_field_id: dynamic_field_id).distinct.pluck(:product_id).each do |product_id|
21
+ SpreeCmCommissioner::MaintenanceTasks::CacheInvalidation
22
+ .pending
23
+ .create_or_find_by(maintainable_type: 'Spree::Product', maintainable_id: product_id)
24
+ end
25
+ end
26
+
16
27
  def parent_field_must_be_selection_type
17
28
  return unless dynamic_field
18
29
 
@@ -311,6 +311,14 @@ module SpreeCmCommissioner
311
311
  Array(public_metadata['eligible_check_in_session_ids']).map(&:to_i)
312
312
  end
313
313
 
314
+ # The buyer's order that took over this ticket. Nil until a transfer out of this guest completes,
315
+ # which is also when the guest is marked transferred.
316
+ def transferred_to_order
317
+ return nil unless transferred?
318
+
319
+ ticket_transfers.completed.order(:completed_at).last&.order
320
+ end
321
+
314
322
  # Set by TicketTransfers::TransferGuest when this guest was created to receive a transferred ticket
315
323
  def from_transfer?
316
324
  ActiveModel::Type::Boolean.new.cast(public_metadata['from_transfer']) || false