spree_cm_commissioner 2.13.5 → 2.13.7

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 (352) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/admin/base_controller_decorator.rb +14 -0
  4. data/app/controllers/spree/admin/customer_notifications_controller.rb +16 -1
  5. data/app/controllers/spree/admin/prepaid_transactions_controller.rb +69 -0
  6. data/app/controllers/spree/admin/private_sales_controller.rb +6 -0
  7. data/app/controllers/spree/admin/product_commissions_controller.rb +2 -1
  8. data/app/controllers/spree/admin/reschedule_policies_controller.rb +148 -0
  9. data/app/controllers/spree/admin/reschedule_requests_controller.rb +82 -0
  10. data/app/controllers/spree/admin/store_credit_admin_controller.rb +122 -0
  11. data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +7 -0
  12. data/app/controllers/spree/api/v2/platform/store_credits_controller_decorator.rb +35 -0
  13. data/app/controllers/spree/api/v2/storefront/account/prepaid_balances_controller.rb +52 -0
  14. data/app/controllers/spree/api/v2/storefront/account/prepaid_transactions_controller.rb +56 -0
  15. data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +37 -0
  16. data/app/controllers/spree/api/v2/storefront/prepaid_config_controller.rb +22 -0
  17. data/app/controllers/spree/api/v2/storefront/reschedule_requests_controller.rb +156 -0
  18. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
  19. data/app/controllers/spree/api/v2/tenant/account/prepaid_balances_controller.rb +51 -0
  20. data/app/controllers/spree/api/v2/tenant/account/prepaid_transactions_controller.rb +53 -0
  21. data/app/controllers/spree/api/v2/tenant/base_controller.rb +20 -0
  22. data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +42 -0
  23. data/app/controllers/spree/api/v2/tenant/prepaid_config_controller.rb +29 -0
  24. data/app/controllers/spree_cm_commissioner/admin/store_credits_controller_decorator.rb +217 -0
  25. data/app/errors/spree_cm_commissioner/reschedule_policies/invalid_policyable_error.rb +8 -0
  26. data/app/errors/spree_cm_commissioner/reschedule_policies/not_reschedulable_error.rb +11 -0
  27. data/app/errors/spree_cm_commissioner/reschedule_requests/cancel_not_allowed_error.rb +6 -0
  28. data/app/errors/spree_cm_commissioner/reschedule_requests/complete_cash_payment_error.rb +6 -0
  29. data/app/errors/spree_cm_commissioner/reschedule_requests/eligibility_denied_error.rb +7 -0
  30. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +6 -2
  31. data/app/factory/spree_cm_commissioner/reschedule_telegram_message_factory.rb +141 -0
  32. data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +1 -1
  33. data/app/helpers/spree/admin/base_helper_decorator.rb +12 -0
  34. data/app/helpers/spree/admin/reschedule_policies_helper.rb +35 -0
  35. data/app/helpers/spree/admin/reschedule_requests_helper.rb +79 -0
  36. data/app/interactors/spree_cm_commissioner/create_ticket.rb +1 -0
  37. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
  38. data/app/jobs/spree_cm_commissioner/prepaid_reconciliation_cron_job.rb +9 -0
  39. data/app/jobs/spree_cm_commissioner/prepaid_reverse_top_up_job.rb +16 -0
  40. data/app/jobs/spree_cm_commissioner/prepaid_setup_types_job.rb +24 -0
  41. data/app/jobs/spree_cm_commissioner/prepaid_top_up_credit_job.rb +31 -0
  42. data/app/jobs/spree_cm_commissioner/reschedule_requests/approval_reminder_job.rb +17 -0
  43. data/app/jobs/spree_cm_commissioner/reschedule_requests/execute_job.rb +20 -0
  44. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_overdue_requests_job.rb +12 -0
  45. data/app/jobs/spree_cm_commissioner/reschedule_requests/expire_request_job.rb +15 -0
  46. data/app/jobs/spree_cm_commissioner/reschedule_requests/payment_reminder_job.rb +17 -0
  47. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
  48. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
  49. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  50. data/app/models/concerns/spree_cm_commissioner/order_prepaid_payment.rb +15 -0
  51. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +56 -8
  52. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  53. data/app/models/concerns/spree_cm_commissioner/reschedule_request_state_machine.rb +332 -0
  54. data/app/models/concerns/spree_cm_commissioner/service_type.rb +1 -1
  55. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  56. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +6 -0
  57. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
  58. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  59. data/app/models/spree/core/controller_helpers/currency_decorator.rb +70 -0
  60. data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
  61. data/app/models/spree_cm_commissioner/configuration.rb +17 -9
  62. data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
  63. data/app/models/spree_cm_commissioner/current.rb +8 -0
  64. data/app/models/spree_cm_commissioner/customer_notification.rb +4 -1
  65. data/app/models/spree_cm_commissioner/exports/import_order_guests_csv.rb +153 -0
  66. data/app/models/spree_cm_commissioner/guest.rb +21 -0
  67. data/app/models/spree_cm_commissioner/integration.rb +31 -0
  68. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +64 -0
  69. data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
  70. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
  71. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
  72. data/app/models/spree_cm_commissioner/inventory_item.rb +12 -0
  73. data/app/models/spree_cm_commissioner/line_item_decorator.rb +6 -2
  74. data/app/models/spree_cm_commissioner/notification.rb +8 -0
  75. data/app/models/spree_cm_commissioner/order_decorator.rb +19 -2
  76. data/app/models/spree_cm_commissioner/payment_decorator.rb +31 -0
  77. data/app/models/spree_cm_commissioner/payment_method_store_credit_decorator.rb +81 -0
  78. data/app/models/spree_cm_commissioner/place.rb +2 -0
  79. data/app/models/spree_cm_commissioner/prepaid_config.rb +59 -0
  80. data/app/models/spree_cm_commissioner/prepaid_transaction.rb +102 -0
  81. data/app/models/spree_cm_commissioner/product_decorator.rb +6 -0
  82. data/app/models/spree_cm_commissioner/reschedule_policy.rb +94 -0
  83. data/app/models/spree_cm_commissioner/reschedule_policy_assignment.rb +19 -0
  84. data/app/models/spree_cm_commissioner/reschedule_request.rb +227 -0
  85. data/app/models/spree_cm_commissioner/reschedule_requests/guest_selection.rb +80 -0
  86. data/app/models/spree_cm_commissioner/reschedule_snapshot.rb +65 -0
  87. data/app/models/spree_cm_commissioner/route.rb +1 -0
  88. data/app/models/spree_cm_commissioner/seat_layout.rb +1 -0
  89. data/app/models/spree_cm_commissioner/service_calendar.rb +4 -0
  90. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -0
  91. data/app/models/spree_cm_commissioner/store_credit_category_decorator.rb +23 -0
  92. data/app/models/spree_cm_commissioner/store_credit_decorator.rb +142 -0
  93. data/app/models/spree_cm_commissioner/store_credit_event_decorator.rb +94 -0
  94. data/app/models/spree_cm_commissioner/store_credit_type_decorator.rb +43 -0
  95. data/app/models/spree_cm_commissioner/store_decorator.rb +8 -0
  96. data/app/models/spree_cm_commissioner/tenant.rb +37 -0
  97. data/app/models/spree_cm_commissioner/trip.rb +2 -1
  98. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
  99. data/app/models/spree_cm_commissioner/user_decorator.rb +79 -0
  100. data/app/models/spree_cm_commissioner/variant_decorator.rb +9 -1
  101. data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
  102. data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
  103. data/app/models/spree_cm_commissioner/vendor_decorator.rb +53 -0
  104. data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
  105. data/app/notifications/spree_cm_commissioner/reschedule_approved_notification.rb +7 -0
  106. data/app/notifications/spree_cm_commissioner/reschedule_cancelled_notification.rb +7 -0
  107. data/app/notifications/spree_cm_commissioner/reschedule_completed_notification.rb +7 -0
  108. data/app/notifications/spree_cm_commissioner/reschedule_expired_notification.rb +7 -0
  109. data/app/notifications/spree_cm_commissioner/reschedule_failed_notification.rb +7 -0
  110. data/app/notifications/spree_cm_commissioner/reschedule_notification_base.rb +41 -0
  111. data/app/notifications/spree_cm_commissioner/reschedule_payment_reminder_notification.rb +15 -0
  112. data/app/notifications/spree_cm_commissioner/reschedule_rejected_notification.rb +7 -0
  113. data/app/notifications/spree_cm_commissioner/reschedule_requested_notification.rb +7 -0
  114. data/app/overrides/spree/admin/products/_form/offline.html.erb.deface +9 -0
  115. data/app/overrides/spree/admin/shared/_main_menu/sidebar_reschedule.html.erb.deface +9 -0
  116. data/app/overrides/spree/admin/shared/_main_menu/sidebar_store_credit.html.erb.deface +8 -0
  117. data/app/overrides/spree/admin/shared/_order_tabs/reschedule_requests.html.erb.deface +10 -0
  118. data/app/presenters/spree_cm_commissioner/prepaid_balance_presenter.rb +16 -0
  119. data/app/queries/spree_cm_commissioner/store_credit_overview_query.rb +75 -0
  120. data/app/queries/spree_cm_commissioner/user_store_credit_summary_query.rb +53 -0
  121. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
  122. data/app/serializers/spree/v2/storefront/prepaid_balance_serializer.rb +15 -0
  123. data/app/serializers/spree/v2/storefront/prepaid_config_serializer.rb +11 -0
  124. data/app/serializers/spree/v2/storefront/prepaid_transaction_serializer.rb +17 -0
  125. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +6 -0
  126. data/app/serializers/spree/v2/tenant/prepaid_balance_serializer.rb +15 -0
  127. data/app/serializers/spree/v2/tenant/prepaid_config_serializer.rb +16 -0
  128. data/app/serializers/spree/v2/tenant/prepaid_transaction_serializer.rb +16 -0
  129. data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +2 -0
  130. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
  131. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_request_serializer.rb +49 -0
  132. data/app/serializers/spree_cm_commissioner/v2/storefront/reschedule_snapshot_serializer.rb +24 -0
  133. data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
  134. data/app/services/spree_cm_commissioner/checkout/add_store_credit_payments.rb +96 -0
  135. data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
  136. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
  137. data/app/services/spree_cm_commissioner/customer_notifications/clone.rb +39 -0
  138. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
  139. data/app/services/spree_cm_commissioner/imports/orders/create.rb +24 -11
  140. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
  141. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
  142. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +230 -0
  143. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
  144. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
  145. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
  146. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +188 -0
  147. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +79 -0
  148. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +354 -0
  149. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
  150. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +93 -0
  151. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +70 -0
  152. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +121 -0
  153. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
  154. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +412 -0
  155. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
  156. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +224 -0
  157. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +769 -0
  158. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +69 -0
  159. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +92 -0
  160. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
  161. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
  162. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
  163. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
  164. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
  165. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
  166. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +386 -0
  167. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
  168. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +237 -0
  169. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +48 -0
  170. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
  171. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +116 -0
  172. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +63 -0
  173. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +314 -0
  174. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
  175. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
  176. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
  177. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
  178. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
  179. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +143 -0
  180. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +325 -0
  181. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
  182. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
  183. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
  184. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
  185. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +171 -0
  186. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +397 -0
  187. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
  188. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
  189. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
  190. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
  191. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
  192. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
  193. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
  194. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
  195. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
  196. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
  197. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
  198. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
  199. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
  200. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
  201. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
  202. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
  203. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
  204. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
  205. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
  206. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
  207. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
  208. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
  209. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +170 -0
  210. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
  211. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
  212. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +92 -0
  213. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +230 -0
  214. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
  215. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
  216. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
  217. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
  218. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
  219. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
  220. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
  221. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +192 -0
  222. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +377 -0
  223. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
  224. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
  225. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
  226. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
  227. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
  228. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
  229. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
  230. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
  231. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
  232. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
  233. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
  234. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
  235. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
  236. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
  237. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +13 -0
  238. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
  239. data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
  240. data/app/services/spree_cm_commissioner/order_holds/release.rb +31 -0
  241. data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
  242. data/app/services/spree_cm_commissioner/prepaid/admin_credit.rb +44 -0
  243. data/app/services/spree_cm_commissioner/prepaid/check_top_up_limits.rb +47 -0
  244. data/app/services/spree_cm_commissioner/prepaid/freeze.rb +58 -0
  245. data/app/services/spree_cm_commissioner/prepaid/mutation.rb +78 -0
  246. data/app/services/spree_cm_commissioner/prepaid/reverse_order_top_up.rb +23 -0
  247. data/app/services/spree_cm_commissioner/prepaid/reverse_top_up.rb +53 -0
  248. data/app/services/spree_cm_commissioner/prepaid/top_up.rb +68 -0
  249. data/app/services/spree_cm_commissioner/prepaid/unfreeze.rb +47 -0
  250. data/app/services/spree_cm_commissioner/prepaid_metrics.rb +37 -0
  251. data/app/services/spree_cm_commissioner/prepaid_reconciliation_service.rb +71 -0
  252. data/app/services/spree_cm_commissioner/reschedule_policies/assign.rb +52 -0
  253. data/app/services/spree_cm_commissioner/reschedule_policies/create.rb +71 -0
  254. data/app/services/spree_cm_commissioner/reschedule_policies/exclude.rb +40 -0
  255. data/app/services/spree_cm_commissioner/reschedule_policies/resolve.rb +109 -0
  256. data/app/services/spree_cm_commissioner/reschedule_policies/unassign.rb +23 -0
  257. data/app/services/spree_cm_commissioner/reschedule_policies/update.rb +73 -0
  258. data/app/services/spree_cm_commissioner/reschedule_requests/authorize_initiator.rb +79 -0
  259. data/app/services/spree_cm_commissioner/reschedule_requests/build_request.rb +317 -0
  260. data/app/services/spree_cm_commissioner/reschedule_requests/calculate_fee.rb +71 -0
  261. data/app/services/spree_cm_commissioner/reschedule_requests/cancel.rb +55 -0
  262. data/app/services/spree_cm_commissioner/reschedule_requests/check_eligibility.rb +243 -0
  263. data/app/services/spree_cm_commissioner/reschedule_requests/collect_payment.rb +204 -0
  264. data/app/services/spree_cm_commissioner/reschedule_requests/execute.rb +252 -0
  265. data/app/services/spree_cm_commissioner/reschedule_requests/expire_overdue_requests.rb +28 -0
  266. data/app/services/spree_cm_commissioner/reschedule_requests/expire_request.rb +24 -0
  267. data/app/services/spree_cm_commissioner/reschedule_requests/guests/update_seats.rb +210 -0
  268. data/app/services/spree_cm_commissioner/reschedule_requests/initiate.rb +255 -0
  269. data/app/services/spree_cm_commissioner/reschedule_requests/issue_compensation.rb +67 -0
  270. data/app/services/spree_cm_commissioner/reschedule_requests/issue_coupon.rb +90 -0
  271. data/app/services/spree_cm_commissioner/reschedule_requests/issue_store_credit.rb +16 -0
  272. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_approval_reminder.rb +43 -0
  273. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_payment_reminder.rb +46 -0
  274. data/app/services/spree_cm_commissioner/reschedule_requests/notifications/send_status_update.rb +42 -0
  275. data/app/services/spree_cm_commissioner/reschedule_requests/seats/hold.rb +137 -0
  276. data/app/services/spree_cm_commissioner/reschedule_requests/split_line_item.rb +86 -0
  277. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_operations.rb +37 -0
  278. data/app/services/spree_cm_commissioner/reschedule_requests/telegram/notify_vendor.rb +51 -0
  279. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/approve.rb +61 -0
  280. data/app/services/spree_cm_commissioner/reschedule_requests/vendors/reject.rb +58 -0
  281. data/app/services/spree_cm_commissioner/topup_draft_orders/create.rb +234 -0
  282. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +25 -14
  283. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +43 -6
  284. data/app/views/spree/admin/classifications/edit.html.erb +17 -2
  285. data/app/views/spree/admin/classifications/new.html.erb +18 -2
  286. data/app/views/spree/admin/customer_notifications/index.html.erb +45 -3
  287. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
  288. data/app/views/spree/admin/integrations/_form.html.erb +12 -0
  289. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
  290. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
  291. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  292. data/app/views/spree/admin/prepaid_transactions/index.html.erb +102 -0
  293. data/app/views/spree/admin/prepaid_transactions/show.html.erb +146 -0
  294. data/app/views/spree/admin/private_sales/_form.html.erb +24 -1
  295. data/app/views/spree/admin/private_sales/_stock.html.erb +6 -1
  296. data/app/views/spree/admin/product_commissions/index.html.erb +7 -2
  297. data/app/views/spree/admin/reschedule_policies/_form.html.erb +65 -0
  298. data/app/views/spree/admin/reschedule_policies/edit.html.erb +11 -0
  299. data/app/views/spree/admin/reschedule_policies/index.html.erb +84 -0
  300. data/app/views/spree/admin/reschedule_policies/new.html.erb +11 -0
  301. data/app/views/spree/admin/reschedule_requests/_cancel_modal.html.erb +34 -0
  302. data/app/views/spree/admin/reschedule_requests/_search_form.html.erb +57 -0
  303. data/app/views/spree/admin/reschedule_requests/_tabs.html.erb +14 -0
  304. data/app/views/spree/admin/reschedule_requests/index.html.erb +64 -0
  305. data/app/views/spree/admin/reschedule_requests/show.html.erb +212 -0
  306. data/app/views/spree/admin/reschedule_requests/state_changes.html.erb +42 -0
  307. data/app/views/spree/admin/shared/_products.html.erb +3 -0
  308. data/app/views/spree/admin/shared/_reschedule_policies_tabs.html.erb +22 -0
  309. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +3 -0
  310. data/app/views/spree/admin/shared/sub_menu/_reschedule.html.erb +4 -0
  311. data/app/views/spree/admin/store_credit_admin/_tabs.html.erb +31 -0
  312. data/app/views/spree/admin/store_credit_admin/_tenant_selector.html.erb +17 -0
  313. data/app/views/spree/admin/store_credit_admin/show.html.erb +200 -0
  314. data/app/views/spree/admin/store_credits/index.html.erb +224 -0
  315. data/app/views/spree/admin/tenants/_form.html.erb +33 -0
  316. data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +28 -5
  317. data/config/initializers/spree_dependencies.rb +3 -0
  318. data/config/initializers/spree_permitted_attributes.rb +1 -0
  319. data/config/locales/en.yml +351 -0
  320. data/config/locales/km.yml +270 -0
  321. data/config/routes.rb +63 -2
  322. data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
  323. data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
  324. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
  325. data/db/migrate/20260506120000_create_cm_reschedule_policies.rb +38 -0
  326. data/db/migrate/20260506120001_create_cm_reschedule_requests.rb +67 -0
  327. data/db/migrate/20260506120002_add_reschedule_count_to_cm_guests.rb +5 -0
  328. data/db/migrate/20260521140000_create_cm_reschedule_snapshots.rb +38 -0
  329. data/db/migrate/20260522160000_create_cm_reschedule_policy_assignments.rb +17 -0
  330. data/db/migrate/20260722000001_create_cm_prepaid_transactions.rb +31 -0
  331. data/db/migrate/20260722000002_add_constraints_to_store_credits.rb +46 -0
  332. data/db/migrate/20260722000003_add_expires_at_to_store_credits.rb +6 -0
  333. data/db/migrate/20260722000004_add_issuer_tenant_id_to_spree_store_credits.rb +7 -0
  334. data/db/migrate/20260722000005_add_immutability_trigger_to_cm_prepaid_transactions.rb +36 -0
  335. data/db/migrate/20260918045558_add_prepaid_currency_columns_to_cm_tenants.rb +6 -0
  336. data/db/migrate/20260923090000_add_public_private_metadata_to_cm_prepaid_transactions.rb +36 -0
  337. data/db/migrate/20261006073317_add_offline_to_spree_products.rb +6 -0
  338. data/lib/spree_cm_commissioner/reschedule_policy_rules.rb +75 -0
  339. data/lib/spree_cm_commissioner/serializable_transaction.rb +38 -0
  340. data/lib/spree_cm_commissioner/store_credit_currency_summary.rb +11 -0
  341. data/lib/spree_cm_commissioner/store_credit_summary.rb +11 -0
  342. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +11 -0
  343. data/lib/spree_cm_commissioner/test_helper/factories/prepaid_transaction_factory.rb +12 -0
  344. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_assignment_factory.rb +7 -0
  345. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_policy_factory.rb +59 -0
  346. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_request_factory.rb +51 -0
  347. data/lib/spree_cm_commissioner/test_helper/factories/reschedule_snapshot_factory.rb +27 -0
  348. data/lib/spree_cm_commissioner/version.rb +1 -1
  349. data/lib/spree_cm_commissioner.rb +4 -0
  350. data/lib/tasks/prepaid_reconciliation_cron_scheduler.rake +7 -0
  351. data/lib/tasks/prepaid_setup_types.rake +7 -0
  352. metadata +269 -2
@@ -0,0 +1,56 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Account
6
+ class PrepaidTransactionsController < Spree::Api::V2::ResourceController
7
+ before_action :require_spree_current_user
8
+
9
+ # See PrepaidTransaction::CUSTOMER_FACING_TYPES for which types these are and why.
10
+ # top_up/admin_credit are the credit-increasing rows; authorize is the negative (hold)
11
+ # row at spend time, already stored with a negative amount (see
12
+ # StoreCreditEventDecorator#signed_amount) so no sign flip is needed here. void is the
13
+ # reversal of an authorize that never captured (order failed/abandoned) -- its amount
14
+ # is positive (money back), pairing with the authorize row it reverses so a "Purchase"
15
+ # doesn't look permanent when it wasn't.
16
+ DISPLAYED_TRANSACTION_TYPES = SpreeCmCommissioner::PrepaidTransaction::CUSTOMER_FACING_TYPES
17
+
18
+ def index
19
+ if params[:currency].present?
20
+ currency = params[:currency].to_s.upcase
21
+ unless Spree::Store.default.supported_currency_codes.include?(currency)
22
+ return render_error_payload("unsupported currency: #{currency}")
23
+ end
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ def collection
30
+ @collection ||= SpreeCmCommissioner::PrepaidTransaction
31
+ .where(store_credit_id: prepaid_credit_ids, transaction_type: DISPLAYED_TRANSACTION_TYPES)
32
+ .includes(order: { payments: :payment_method })
33
+ .order(created_at: :desc)
34
+ end
35
+
36
+ private
37
+
38
+ # Mirrors PrepaidBalancesController#presented_collection -- an optional ?currency=
39
+ # narrows to one wallet, omitted returns transactions across every currency the user
40
+ # holds. Previously pinned to current_currency (session/store default), which had no
41
+ # way to request a non-default wallet's history, so a KHR top-up never showed up here
42
+ # even though its cm_prepaid_transactions row was written correctly.
43
+ def prepaid_credit_ids
44
+ currency = params[:currency].presence&.upcase
45
+ spree_current_user.store_credits.platform_prepaid(currency).pluck(:id)
46
+ end
47
+
48
+ def collection_serializer
49
+ Spree::V2::Storefront::PrepaidTransactionSerializer
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -9,6 +9,10 @@ module Spree
9
9
 
10
10
  before_action -> { ensure_anonymous_booking_allowed!(draft_order_products) }, only: :create
11
11
 
12
+ # A top-up credits a wallet, which must belong to a real user -- unlike #create,
13
+ # this action has no guest-checkout path.
14
+ before_action :require_spree_current_user, only: [:topup]
15
+
12
16
  # Creates a generic draft order from a locally-assembled cart in a single request.
13
17
  # Endpoint: POST /api/v2/storefront/draft_orders
14
18
  # Params:
@@ -36,6 +40,39 @@ module Spree
36
40
  end
37
41
  end
38
42
 
43
+ # Builds a prepaid top-up order from just an amount and a payment method -- the client
44
+ # never fetches/knows the prepaid_top_up product or variant.
45
+ # Endpoint: POST /api/v2/storefront/draft_orders/topup
46
+ # Params:
47
+ # - amount: the custom top-up amount (required)
48
+ # - payment_method_id: id of the payment method to attach (required)
49
+ # - currency: the wallet currency the client's own currency picker selected (optional --
50
+ # TopupDraftOrders::Create falls back to store.default_currency when nil). Deliberately
51
+ # NOT current_currency: that helper resolves general storefront browsing currency
52
+ # (session -> ?currency= -> store default) -- a stale session[:currency] would
53
+ # silently override what the client explicitly picked here, leading to the amount
54
+ # being validated against the wrong currency's min/max (e.g. a KHR amount checked
55
+ # against the USD cap).
56
+ def topup
57
+ result = SpreeCmCommissioner::TopupDraftOrders::Create.call(
58
+ amount: params[:amount],
59
+ payment_method_id: params[:payment_method_id],
60
+ user: spree_current_user,
61
+ store: current_store,
62
+ currency: params[:currency].presence&.upcase,
63
+ options: {
64
+ device_id: device_id,
65
+ last_ip_address: request.remote_ip
66
+ }
67
+ )
68
+
69
+ if result.success?
70
+ render_serialized_payload(201) { serialize_resource(serializable_order(result.value[:order])) }
71
+ else
72
+ render_error_payload(result.error)
73
+ end
74
+ end
75
+
39
76
  private
40
77
 
41
78
  # Re-fetch the created order with everything the enriched payload touches eager-loaded, so
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ # Endpoint: GET /api/v2/storefront/prepaid_config
6
+ # Public (no sign-in required): a guest should be able to discover currencies/limits
7
+ # before a top-up is submitted.
8
+ class PrepaidConfigController < Spree::Api::V2::ResourceController
9
+ def show
10
+ render_serialized_payload { serialize_resource(SpreeCmCommissioner::PrepaidConfig.resolve) }
11
+ end
12
+
13
+ private
14
+
15
+ def resource_serializer
16
+ Spree::V2::Storefront::PrepaidConfigSerializer
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,156 @@
1
+ # Customer-initiated reschedule lifecycle, scoped to the signed-in customer's
2
+ # own bookings. Every action delegates to the same RescheduleRequests::*
3
+ # services the vendor console drives.
4
+ #
5
+ # Actions:
6
+ # - index/show - the customer's own requests only, scoped by `load_request!`
7
+ # - preview - quote only, nothing persisted, terms not required yet
8
+ # - create - persists and auto-advances per policy
9
+ # - pay - online balance-due payment
10
+ # - cancel - withdraws a request already in flight
11
+ #
12
+
13
+ module Spree
14
+ module Api
15
+ module V2
16
+ module Storefront
17
+ class RescheduleRequestsController < ::Spree::Api::V2::ResourceController
18
+ before_action :require_spree_current_user
19
+
20
+ def index
21
+ render_serialized_payload { serialize_collection(paginated_collection) }
22
+ end
23
+
24
+ def show
25
+ render_serialized_payload { serialize_resource(load_request!) }
26
+ end
27
+
28
+ def preview
29
+ result = run_initiate(preview: true)
30
+ return render_error_payload(result.error) if result.failure?
31
+
32
+ render_serialized_payload { { data: preview_payload(result.value) } }
33
+ end
34
+
35
+ def create
36
+ return render_error_payload(I18n.t('reschedule_requests.initiate.terms_not_accepted')) unless terms_accepted?
37
+
38
+ result = run_initiate(
39
+ preview: false,
40
+ terms_accepted_at: Time.current,
41
+ reason: reschedule_params[:reason]
42
+ )
43
+ return render_error_payload(result.error) if result.failure?
44
+
45
+ render_serialized_payload(201) { serialize_resource(result.value[:reschedule_request]) }
46
+ end
47
+
48
+ def cancel
49
+ result = SpreeCmCommissioner::RescheduleRequests::Cancel.call(
50
+ reschedule_request: load_request!,
51
+ cancelled_by: spree_current_user,
52
+ reason: params[:reason]
53
+ )
54
+ return render_error_payload(result.error) if result.failure?
55
+
56
+ render_serialized_payload { serialize_resource(result.value[:reschedule_request]) }
57
+ end
58
+
59
+ def pay
60
+ result = SpreeCmCommissioner::RescheduleRequests::CollectPayment.call(
61
+ reschedule_request: load_request!,
62
+ payment_params: payment_params,
63
+ acting_user: spree_current_user
64
+ )
65
+ return render_error_payload(result.error) if result.failure?
66
+
67
+ render_serialized_payload do
68
+ Spree::V2::Storefront::PaymentSerializer.new(result.value[:payment], params: serializer_params).serializable_hash
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def terms_accepted?
75
+ ActiveModel::Type::Boolean.new.cast(reschedule_params[:terms_accepted])
76
+ end
77
+
78
+ def run_initiate(preview:, terms_accepted_at: nil, reason: nil)
79
+ SpreeCmCommissioner::RescheduleRequests::Initiate.call(
80
+ order_id: reschedule_params[:order_number],
81
+ line_item_ids: reschedule_params[:line_item_ids],
82
+ new_from_date: reschedule_params[:new_from_date],
83
+ new_to_date: reschedule_params[:new_to_date],
84
+ targets: reschedule_params[:targets],
85
+ initiated_by: spree_current_user,
86
+ initiated_as: 'user',
87
+ options: {
88
+ preview: preview,
89
+ terms_accepted_at: terms_accepted_at,
90
+ reason: reason,
91
+ request_ip: request.remote_ip
92
+ }.compact
93
+ )
94
+ end
95
+
96
+ def load_request!
97
+ SpreeCmCommissioner::RescheduleRequest
98
+ .for_user(spree_current_user.id)
99
+ .find_by!(number: params[:id])
100
+ end
101
+
102
+ def collection
103
+ SpreeCmCommissioner::RescheduleRequest
104
+ .for_user(spree_current_user.id)
105
+ .includes(:order, :previous_request_snapshots, :new_request_snapshots)
106
+ .order(id: :desc)
107
+ end
108
+
109
+ def default_resource_includes
110
+ %w[before_snapshots after_snapshots]
111
+ end
112
+
113
+ def resource_serializer
114
+ SpreeCmCommissioner::V2::Storefront::RescheduleRequestSerializer
115
+ end
116
+
117
+ def collection_serializer
118
+ SpreeCmCommissioner::V2::Storefront::RescheduleRequestSerializer
119
+ end
120
+
121
+ def preview_payload(value)
122
+ request = value[:reschedule_request]
123
+ {
124
+ fee_amount: request.fee_amount,
125
+ amount_due_total: request.amount_due_total,
126
+ credit_amount_total: request.credit_amount_total,
127
+ currency: request.currency,
128
+ payment_required: request.payment_required?,
129
+ requires_vendor_approval: value[:requires_vendor_approval],
130
+ fee_breakdown: value[:fee_breakdown]
131
+ }
132
+ end
133
+
134
+ def reschedule_params
135
+ params.require(:reschedule_request).permit(
136
+ :order_number, :new_from_date, :new_to_date, :reason, :terms_accepted,
137
+ line_item_ids: [],
138
+ targets: [
139
+ :line_item_id, :new_from_date, :new_to_date, :trip_id,
140
+ :boarding_trip_stop_id, :drop_off_trip_stop_id, :variant_id,
141
+ { guest_ids: [], block_ids: [] }
142
+ ]
143
+ )
144
+ end
145
+
146
+ def payment_params
147
+ params.require(:payment).permit(
148
+ :payment_method_id, :payment_option, :gateway_payment_profile_id,
149
+ :gateway_customer_profile_id, :last_digits, :month, :year, :name
150
+ )
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,66 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Transit
6
+ class ItemAvailabilitiesController < ::Spree::Api::V2::ResourceController
7
+ include SpreeCmCommissioner::Integrations::InventorySyncCachable
8
+
9
+ def create
10
+ sync_result = sync_item_availability!
11
+
12
+ unless sync_result.success?
13
+ error_message = sync_result.to_h.dig(:error, :message) || 'Sync failed'
14
+ return render_error_payload(error_message, 422)
15
+ end
16
+
17
+ item_availability = SpreeCmCommissioner::ItemAvailabilitySync.new(
18
+ id: trip.id,
19
+ last_sync_at: sync_result.last_synced_at
20
+ )
21
+ render_serialized_payload { serialize_resource(item_availability) }
22
+ rescue ActiveRecord::RecordNotFound => e
23
+ render_error_payload(e.message, 404)
24
+ end
25
+
26
+ private
27
+
28
+ def trip
29
+ @trip ||= SpreeCmCommissioner::Trip.find(params[:trip_id])
30
+ end
31
+
32
+ def integration
33
+ @integration ||= trip.vendor&.integration
34
+ end
35
+
36
+ def sync_item_availability!
37
+ # Return success result if vendor has no integration
38
+ return SpreeCmCommissioner::Integrations::Base::SyncResult.new if integration.nil?
39
+
40
+ # Check cache first for successful results only
41
+ cached_result = Rails.cache.read(sync_cache_key)
42
+ return cached_result if cached_result
43
+
44
+ # Perform sync
45
+ result = integration.sync_item_availability!(trip_id: trip.id, on_date: params[:on_date])
46
+
47
+ # Only cache successful results to avoid sticky failures
48
+ Rails.cache.write(sync_cache_key, result, expires_in: sync_cache_ttl) if result.success?
49
+
50
+ result
51
+ end
52
+
53
+ def sync_cache_key
54
+ # Include trip.updated_at to auto-bust cache when trip is updated by background syncs
55
+ "item_availability_syncs:#{trip.id}:#{params[:on_date]}:#{trip.updated_at.to_i}"
56
+ end
57
+
58
+ def resource_serializer
59
+ SpreeCmCommissioner::V2::Storefront::ItemAvailabilitySyncSerializer
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,51 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ module Account
6
+ # Endpoint: GET /api/v2/tenant/account/prepaid_balances
7
+ # Single endpoint for both "my one balance" and "all my balances" -- an optional
8
+ # ?currency= filters down to one (still an array, just 0-or-1 items; hard-rejected if
9
+ # outside the tenant's supported_currencies_list, via BaseController#
10
+ # resolve_requested_currency), omitted returns every currency this tenant has issued the
11
+ # user (ignoring supported_currencies_list -- a balance in a since-unsupported currency
12
+ # stays visible/spendable). Replaces the old separate GET account/prepaid (show) +
13
+ # GET account/prepaid_balances (index) pair -- see Storefront::Account::
14
+ # PrepaidController's own comment for why one endpoint with a fixed array shape beats
15
+ # two endpoints whose response type differs by param.
16
+ class PrepaidBalancesController < BaseController
17
+ before_action :require_spree_current_user
18
+
19
+ def index
20
+ currency = nil
21
+ if params[:currency].present?
22
+ currency, error = resolve_requested_currency
23
+ return render_error_payload(error) if error
24
+ end
25
+
26
+ render_serialized_payload { collection_serializer.new(presented_collection(currency)).serializable_hash }
27
+ end
28
+
29
+ private
30
+
31
+ # Tenant-issued prepaid credit only (Spree::StoreCredit#tenant_prepaid) -- unlike the
32
+ # storefront version, this is never platform-issued credit: a tenant's own wallet
33
+ # screen shows only what that tenant itself issued this user. Preferred currency
34
+ # sorted first -- see Storefront::Account::PrepaidBalancesController's own comment.
35
+ def presented_collection(currency)
36
+ preferred_currency = @tenant.preferred_currency_or_default
37
+ credits = spree_current_user.store_credits.tenant_prepaid(@tenant.id, currency).to_a
38
+ credits.sort_by! { |credit| credit.currency == preferred_currency ? 0 : 1 }
39
+
40
+ credits.map { |credit| SpreeCmCommissioner::PrepaidBalancePresenter.new(credit, preferred_currency) }
41
+ end
42
+
43
+ def collection_serializer
44
+ Spree::V2::Tenant::PrepaidBalanceSerializer
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ module Account
6
+ # Endpoint: GET /api/v2/tenant/account/prepaid_transactions
7
+ # Mirrors Spree::Api::V2::Storefront::Account::PrepaidTransactionsController, scoped to
8
+ # this tenant's own issued credit only (Spree::StoreCredit#tenant_prepaid) -- same
9
+ # scoping split as Tenant::Account::PrepaidController vs its storefront counterpart.
10
+ class PrepaidTransactionsController < BaseController
11
+ before_action :require_spree_current_user
12
+
13
+ # See PrepaidTransaction::CUSTOMER_FACING_TYPES for which types these are and why.
14
+ DISPLAYED_TRANSACTION_TYPES = SpreeCmCommissioner::PrepaidTransaction::CUSTOMER_FACING_TYPES
15
+
16
+ # Overridden (rather than left to ResourceController#index) so an out-of-list
17
+ # ?currency= is a hard rejection here too, same as Tenant::Account::PrepaidController
18
+ # #show -- without this check, collection's `prepaid_credit&.id` would just silently
19
+ # render an empty list for a bad currency instead of telling the client why.
20
+ def index
21
+ _currency, error = resolve_requested_currency
22
+ return render_error_payload(error) if error
23
+
24
+ super
25
+ end
26
+
27
+ def collection
28
+ @collection ||= SpreeCmCommissioner::PrepaidTransaction
29
+ .where(store_credit_id: prepaid_credit&.id, transaction_type: DISPLAYED_TRANSACTION_TYPES)
30
+ .includes(order: { payments: :payment_method })
31
+ .order(created_at: :desc)
32
+ end
33
+
34
+ private
35
+
36
+ # Same currency resolution as Tenant::Account::PrepaidController#show -- the currency
37
+ # this tenant's wallet screen is currently viewing, not every currency at once (that's
38
+ # what prepaid_balances is for). #index already rejected a bad currency above, so
39
+ # resolve_requested_currency's error here is always nil by the time collection runs.
40
+ def prepaid_credit
41
+ currency, = resolve_requested_currency
42
+ @prepaid_credit ||= spree_current_user.store_credits.tenant_prepaid(@tenant.id, currency).take
43
+ end
44
+
45
+ def collection_serializer
46
+ Spree::V2::Tenant::PrepaidTransactionSerializer
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -20,6 +20,26 @@ module Spree
20
20
  @current_vendor ||= @tenant.active_vendor
21
21
  end
22
22
 
23
+ # current_currency/supported_currency?/supported_currencies are tenant-aware via
24
+ # Spree::Core::ControllerHelpers::CurrencyDecorator (keys off @tenant, set above) --
25
+ # see that file for the tenant-vs-store resolution logic.
26
+
27
+ # Resolves the wallet currency for a tenant-scoped prepaid request: an explicit
28
+ # ?currency= param must be inside this tenant's own supported_currencies_list -- a hard
29
+ # rejection, not a silent fallback, per the design doc (a currency outside the
30
+ # tenant's configured set would otherwise make the restriction cosmetic). Omitted,
31
+ # falls back to Tenant#preferred_currency_or_default. Returns [currency, error_message]
32
+ # -- error_message is nil on success.
33
+ def resolve_requested_currency
34
+ requested = params[:currency].presence
35
+ return [@tenant.preferred_currency_or_default, nil] if requested.blank?
36
+
37
+ currency = requested.to_s.upcase
38
+ return [nil, "unsupported currency: #{currency}"] unless @tenant.supports_currency?(currency)
39
+
40
+ [currency, nil]
41
+ end
42
+
23
43
  def render_serialized_payload(status = 200)
24
44
  render json: yield, status: status, content_type: content_type
25
45
  end
@@ -7,6 +7,10 @@ module Spree
7
7
 
8
8
  before_action -> { ensure_anonymous_booking_allowed!(draft_order_products) }, only: :create
9
9
 
10
+ # A top-up credits a wallet, which must belong to a real user -- unlike #create,
11
+ # this action has no guest-checkout path.
12
+ before_action :require_spree_current_user, only: [:topup]
13
+
10
14
  # Creates a generic draft order from a locally-assembled cart in a single request.
11
15
  # Endpoint: POST /api/v2/tenant/draft_orders
12
16
  # Params:
@@ -33,6 +37,44 @@ module Spree
33
37
  end
34
38
  end
35
39
 
40
+ # Builds a prepaid top-up order from just an amount and a payment method -- the client
41
+ # never fetches/knows the prepaid_top_up product or variant. Tenant-scoped: the resulting
42
+ # order.tenant_id makes PrepaidTopUpCreditJob produce tenant-issued (not platform-issued)
43
+ # credit.
44
+ # Endpoint: POST /api/v2/tenant/draft_orders/topup
45
+ # Params:
46
+ # - amount: the custom top-up amount (required)
47
+ # - payment_method_id: id of the payment method to attach (required)
48
+ # - currency: optional -- must be inside this tenant's own supported_currencies_list if
49
+ # given; omitted, resolves to Tenant#preferred_currency_or_default (see
50
+ # BaseController#resolve_requested_currency). This deliberately doesn't read
51
+ # current_currency here even though it's tenant-aware now too -- resolve_requested_currency
52
+ # hard-rejects an out-of-list currency (422), where current_currency would silently
53
+ # fall back instead, which would make this endpoint's own validation cosmetic.
54
+ def topup
55
+ currency, error = resolve_requested_currency
56
+ return render_error_payload(error) if error
57
+
58
+ result = SpreeCmCommissioner::TopupDraftOrders::Create.call(
59
+ amount: params[:amount],
60
+ payment_method_id: params[:payment_method_id],
61
+ user: spree_current_user,
62
+ store: current_store,
63
+ currency: currency,
64
+ tenant: @tenant,
65
+ options: {
66
+ device_id: params[:device_id],
67
+ last_ip_address: request.remote_ip
68
+ }
69
+ )
70
+
71
+ if result.success?
72
+ render_serialized_payload(201) { serialize_resource(serializable_order(result.value[:order])) }
73
+ else
74
+ render_error_payload(result.error)
75
+ end
76
+ end
77
+
36
78
  private
37
79
 
38
80
  # Re-fetch the created order with everything the enriched payload touches eager-loaded, so
@@ -0,0 +1,29 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ # Endpoint: GET /api/v2/tenant/prepaid_config
6
+ # Mirrors Spree::Api::V2::Storefront::PrepaidConfigController, resolved with this tenant
7
+ # so supported_currencies/preferred_currency reflect Tenant#supported_currencies_list/
8
+ # #preferred_currency_or_default -- the same values BaseController#
9
+ # resolve_requested_currency already validates/defaults `?currency=` against on
10
+ # GET account/prepaid_balances and POST draft_orders/topup, now discoverable up front
11
+ # instead of only enforced after the fact on submit. Not under account/ -- this is
12
+ # tenant-wide config, not per-user account data (identical response for every user).
13
+ class PrepaidConfigController < BaseController
14
+ before_action :require_spree_current_user
15
+
16
+ def show
17
+ render_serialized_payload { serialize_resource(SpreeCmCommissioner::PrepaidConfig.resolve(tenant: @tenant)) }
18
+ end
19
+
20
+ private
21
+
22
+ def resource_serializer
23
+ Spree::V2::Tenant::PrepaidConfigSerializer
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end