spree_api 5.4.3 → 5.5.0.rc1

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +19 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -0
  4. data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +149 -0
  5. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +54 -0
  6. data/app/controllers/concerns/spree/api/v3/bulk_operations.rb +103 -0
  7. data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +60 -0
  8. data/app/controllers/concerns/spree/api/v3/error_handler.rb +4 -0
  9. data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +84 -0
  10. data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +88 -0
  11. data/app/controllers/concerns/spree/api/v3/store/search_provider_support.rb +35 -1
  12. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +97 -0
  13. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +25 -0
  14. data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +55 -0
  15. data/app/controllers/spree/api/v3/admin/auth_controller.rb +134 -0
  16. data/app/controllers/spree/api/v3/admin/base_controller.rb +3 -17
  17. data/app/controllers/spree/api/v3/admin/categories_controller.rb +25 -0
  18. data/app/controllers/spree/api/v3/admin/channels_controller.rb +65 -0
  19. data/app/controllers/spree/api/v3/admin/countries_controller.rb +38 -0
  20. data/app/controllers/spree/api/v3/admin/coupon_codes_controller.rb +33 -0
  21. data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +34 -0
  22. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +108 -0
  23. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +31 -0
  24. data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +88 -0
  25. data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +31 -0
  26. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +93 -0
  27. data/app/controllers/spree/api/v3/admin/customers_controller.rb +119 -0
  28. data/app/controllers/spree/api/v3/admin/dashboard_controller.rb +44 -0
  29. data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +40 -0
  30. data/app/controllers/spree/api/v3/admin/exports_controller.rb +89 -0
  31. data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +31 -0
  32. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +33 -0
  33. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +138 -0
  34. data/app/controllers/spree/api/v3/admin/invitations_controller.rb +70 -0
  35. data/app/controllers/spree/api/v3/admin/markets_controller.rb +42 -0
  36. data/app/controllers/spree/api/v3/admin/me_controller.rb +69 -0
  37. data/app/controllers/spree/api/v3/admin/media_controller.rb +119 -0
  38. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +34 -0
  39. data/app/controllers/spree/api/v3/admin/orders/adjustments_controller.rb +27 -0
  40. data/app/controllers/spree/api/v3/admin/orders/base_controller.rb +26 -0
  41. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +104 -0
  42. data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +79 -0
  43. data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +92 -0
  44. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +90 -0
  45. data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +53 -0
  46. data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +59 -0
  47. data/app/controllers/spree/api/v3/admin/orders_controller.rb +190 -0
  48. data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +73 -0
  49. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +156 -0
  50. data/app/controllers/spree/api/v3/admin/prices_controller.rb +129 -0
  51. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +48 -0
  52. data/app/controllers/spree/api/v3/admin/products_controller.rb +237 -0
  53. data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +78 -0
  54. data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +56 -0
  55. data/app/controllers/spree/api/v3/admin/promotions_controller.rb +78 -0
  56. data/app/controllers/spree/api/v3/admin/resource_controller.rb +29 -11
  57. data/app/controllers/spree/api/v3/admin/roles_controller.rb +29 -0
  58. data/app/controllers/spree/api/v3/admin/stock_items_controller.rb +35 -0
  59. data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +36 -0
  60. data/app/controllers/spree/api/v3/admin/stock_reservations_controller.rb +29 -0
  61. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +75 -0
  62. data/app/controllers/spree/api/v3/admin/store_controller.rb +53 -0
  63. data/app/controllers/spree/api/v3/admin/store_credit_categories_controller.rb +21 -0
  64. data/app/controllers/spree/api/v3/admin/tags_controller.rb +51 -0
  65. data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +21 -0
  66. data/app/controllers/spree/api/v3/admin/variants_controller.rb +33 -0
  67. data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +49 -0
  68. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +75 -0
  69. data/app/controllers/spree/api/v3/resource_controller.rb +90 -8
  70. data/app/controllers/spree/api/v3/store/auth_controller.rb +8 -28
  71. data/app/controllers/spree/api/v3/store/base_controller.rb +6 -0
  72. data/app/controllers/spree/api/v3/store/carts_controller.rb +1 -0
  73. data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -0
  74. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +77 -0
  75. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +2 -2
  76. data/app/controllers/spree/api/v3/store/products_controller.rb +3 -3
  77. data/app/controllers/spree/api/v3/store/resource_controller.rb +10 -2
  78. data/app/jobs/spree/webhook_delivery_job.rb +5 -0
  79. data/app/models/spree/api_key_ability.rb +16 -0
  80. data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -6
  81. data/app/serializers/spree/api/v3/admin/adjustment_serializer.rb +3 -15
  82. data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +19 -3
  83. data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +2 -6
  84. data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +42 -0
  85. data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
  86. data/app/serializers/spree/api/v3/admin/channel_serializer.rb +15 -0
  87. data/app/serializers/spree/api/v3/admin/country_serializer.rb +1 -1
  88. data/app/serializers/spree/api/v3/admin/coupon_code_serializer.rb +30 -0
  89. data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +4 -2
  90. data/app/serializers/spree/api/v3/admin/custom_field_definition_serializer.rb +21 -0
  91. data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +8 -3
  92. data/app/serializers/spree/api/v3/admin/customer_group_serializer.rb +27 -0
  93. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +58 -2
  94. data/app/serializers/spree/api/v3/admin/dashboard_analytics_serializer.rb +143 -0
  95. data/app/serializers/spree/api/v3/admin/export_serializer.rb +40 -0
  96. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -6
  97. data/app/serializers/spree/api/v3/admin/{asset_serializer.rb → gift_card_batch_serializer.rb} +1 -1
  98. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +39 -4
  99. data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +64 -0
  100. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +4 -16
  101. data/app/serializers/spree/api/v3/admin/media_serializer.rb +24 -2
  102. data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +4 -1
  103. data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +4 -1
  104. data/app/serializers/spree/api/v3/admin/order_serializer.rb +21 -6
  105. data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +11 -2
  106. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -6
  107. data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +4 -1
  108. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +51 -0
  109. data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +55 -0
  110. data/app/serializers/spree/api/v3/admin/price_serializer.rb +4 -0
  111. data/app/serializers/spree/api/v3/admin/product_publication_serializer.rb +11 -0
  112. data/app/serializers/spree/api/v3/admin/product_serializer.rb +34 -10
  113. data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +71 -0
  114. data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +85 -0
  115. data/app/serializers/spree/api/v3/admin/promotion_serializer.rb +41 -0
  116. data/app/serializers/spree/api/v3/admin/refund_serializer.rb +4 -2
  117. data/app/serializers/spree/api/v3/admin/role_serializer.rb +17 -0
  118. data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +16 -1
  119. data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +11 -2
  120. data/app/serializers/spree/api/v3/admin/stock_reservation_serializer.rb +46 -0
  121. data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +37 -0
  122. data/app/serializers/spree/api/v3/admin/store_credit_category_serializer.rb +19 -0
  123. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +11 -5
  124. data/app/serializers/spree/api/v3/admin/store_serializer.rb +55 -0
  125. data/app/serializers/spree/api/v3/admin/tax_category_serializer.rb +4 -2
  126. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +37 -6
  127. data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +45 -0
  128. data/app/serializers/spree/api/v3/admin/webhook_endpoint_serializer.rb +69 -0
  129. data/app/serializers/spree/api/v3/channel_serializer.rb +14 -0
  130. data/app/serializers/spree/api/v3/custom_field_serializer.rb +9 -10
  131. data/app/serializers/spree/api/v3/customer_serializer.rb +5 -0
  132. data/app/serializers/spree/api/v3/market_serializer.rb +2 -1
  133. data/app/serializers/spree/api/v3/media_serializer.rb +8 -6
  134. data/app/serializers/spree/api/v3/order_serializer.rb +6 -1
  135. data/app/serializers/spree/api/v3/payment_method_serializer.rb +11 -2
  136. data/app/serializers/spree/api/v3/product_publication_serializer.rb +22 -0
  137. data/app/serializers/spree/api/v3/product_serializer.rb +6 -1
  138. data/app/serializers/spree/api/v3/stock_reservation_serializer.rb +10 -0
  139. data/config/locales/en.yml +2 -0
  140. data/config/routes.rb +235 -1
  141. data/lib/spree/api/configuration.rb +2 -2
  142. data/lib/spree/api/dependencies.rb +25 -1
  143. data/lib/spree/api/openapi/path_sorter.rb +126 -0
  144. data/lib/spree/api/openapi/schema_helper.rb +185 -6
  145. metadata +96 -8
  146. data/app/serializers/spree/api/v3/admin/shipping_category_serializer.rb +0 -14
@@ -0,0 +1,79 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ module Orders
6
+ class GiftCardsController < BaseController
7
+ scoped_resource :gift_cards
8
+
9
+ skip_before_action :set_resource, raise: false
10
+
11
+ # POST /api/v3/admin/orders/:order_id/gift_cards
12
+ #
13
+ # Body: { code: String }
14
+ def create
15
+ with_order_lock do
16
+ gift_card = find_gift_card!
17
+ return unless gift_card
18
+
19
+ result = @parent.apply_gift_card(gift_card)
20
+
21
+ if result.success?
22
+ render json: serializer_class.new(gift_card).to_h, status: :created
23
+ else
24
+ render_service_error(result.error)
25
+ end
26
+ end
27
+ end
28
+
29
+ # DELETE /api/v3/admin/orders/:order_id/gift_cards/:id
30
+ def destroy
31
+ with_order_lock do
32
+ result = @parent.remove_gift_card
33
+
34
+ if result.success?
35
+ head :no_content
36
+ else
37
+ render_service_error(result.error)
38
+ end
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def model_class
45
+ Spree::GiftCard
46
+ end
47
+
48
+ def serializer_class
49
+ Spree.api.admin_gift_card_serializer
50
+ end
51
+
52
+ private
53
+
54
+ def find_gift_card!
55
+ gift_card = current_store.gift_cards.find_by(code: params[:code]&.downcase)
56
+
57
+ if gift_card.nil?
58
+ render_error(code: ERROR_CODES[:gift_card_not_found], message: Spree.t(:gift_card_not_found), status: :not_found)
59
+ return
60
+ end
61
+
62
+ if gift_card.expired?
63
+ render_error(code: ERROR_CODES[:gift_card_expired], message: Spree.t(:gift_card_expired), status: :unprocessable_content)
64
+ return
65
+ end
66
+
67
+ if gift_card.redeemed?
68
+ render_error(code: ERROR_CODES[:gift_card_already_redeemed], message: Spree.t(:gift_card_already_redeemed), status: :unprocessable_content)
69
+ return
70
+ end
71
+
72
+ gift_card
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,92 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ module Orders
6
+ class ItemsController < BaseController
7
+ scoped_resource :orders
8
+
9
+ # POST /api/v3/admin/orders/:order_id/items
10
+ def create
11
+ with_order_lock do
12
+ result = Spree.cart_add_item_service.call(
13
+ order: @parent,
14
+ variant: variant,
15
+ quantity: permitted_params[:quantity] || 1,
16
+ options: permitted_params[:options] || {}
17
+ )
18
+
19
+ if result.success?
20
+ render json: serialize_resource(result.value), status: :created
21
+ else
22
+ render_service_error(result.error, code: ERROR_CODES[:insufficient_stock])
23
+ end
24
+ end
25
+ end
26
+
27
+ # PATCH /api/v3/admin/orders/:order_id/items/:id
28
+ def update
29
+ with_order_lock do
30
+ if permitted_params[:quantity].present?
31
+ result = Spree.cart_set_item_quantity_service.call(
32
+ order: @parent,
33
+ line_item: @resource,
34
+ quantity: permitted_params[:quantity]
35
+ )
36
+
37
+ if result.success?
38
+ render json: serialize_resource(@resource.reload)
39
+ else
40
+ render_service_error(result.error, code: ERROR_CODES[:invalid_quantity])
41
+ end
42
+ else
43
+ if @resource.update(permitted_params.except(:variant_id, :quantity))
44
+ render json: serialize_resource(@resource)
45
+ else
46
+ render_errors(@resource.errors)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # DELETE /api/v3/admin/orders/:order_id/items/:id
53
+ def destroy
54
+ with_order_lock do
55
+ Spree.cart_remove_line_item_service.call(
56
+ order: @parent,
57
+ line_item: @resource
58
+ )
59
+
60
+ head :no_content
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ def model_class
67
+ Spree::LineItem
68
+ end
69
+
70
+ def serializer_class
71
+ Spree.api.admin_line_item_serializer
72
+ end
73
+
74
+ def parent_association
75
+ :line_items
76
+ end
77
+
78
+ def permitted_params
79
+ params.permit(:variant_id, :quantity, metadata: {}, options: {})
80
+ end
81
+
82
+ private
83
+
84
+ def variant
85
+ @variant ||= current_store.variants.find_by_prefix_id!(permitted_params[:variant_id])
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,90 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ module Orders
6
+ class PaymentsController < BaseController
7
+ scoped_resource :payments
8
+
9
+ before_action :set_resource, only: [:show, :capture, :void]
10
+
11
+ # POST /api/v3/admin/orders/:order_id/payments
12
+ #
13
+ # Supports off-session admin charges by passing `source_id` referencing a
14
+ # saved payment source (e.g. a Spree::CreditCard owned by the order's customer).
15
+ # The source must belong to the customer assigned to the order.
16
+ def create
17
+ with_order_lock do
18
+ payment_method = Spree::PaymentMethod.find_by_prefix_id!(params[:payment_method_id])
19
+ @resource = @parent.payments.build(
20
+ amount: params[:amount] || @parent.order_total_after_store_credit,
21
+ payment_method: payment_method
22
+ )
23
+
24
+ if params[:source_id].present? && payment_method.source_required?
25
+ @resource.source = find_source!(payment_method, params[:source_id])
26
+ end
27
+
28
+ authorize_resource!(@resource, :create)
29
+
30
+ if @resource.save
31
+ render json: serialize_resource(@resource), status: :created
32
+ else
33
+ render_validation_error(@resource.errors)
34
+ end
35
+ end
36
+ end
37
+
38
+ # PATCH /api/v3/admin/orders/:order_id/payments/:id/capture
39
+ def capture
40
+ with_order_lock do
41
+ amount = params[:amount] ? (params[:amount].to_f * 100).round : nil
42
+ @resource.capture!(amount)
43
+ render json: serialize_resource(@resource.reload)
44
+ rescue Spree::Core::GatewayError => e
45
+ render_service_error(e.message)
46
+ end
47
+ end
48
+
49
+ # PATCH /api/v3/admin/orders/:order_id/payments/:id/void
50
+ def void
51
+ with_order_lock do
52
+ @resource.void_transaction!
53
+ render json: serialize_resource(@resource.reload)
54
+ rescue Spree::Core::GatewayError => e
55
+ render_service_error(e.message)
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ def model_class
62
+ Spree::Payment
63
+ end
64
+
65
+ def serializer_class
66
+ Spree.api.admin_payment_serializer
67
+ end
68
+
69
+ def parent_association
70
+ :payments
71
+ end
72
+
73
+ def permitted_params
74
+ params.permit(:amount, :payment_method_id, :source_id)
75
+ end
76
+
77
+ # Saved-source charges require an order customer — sources are scoped
78
+ # to that customer to prevent attaching customer A's card to
79
+ # customer B's order. Refuse if no customer is assigned.
80
+ def find_source!(payment_method, source_id)
81
+ raise ActiveRecord::RecordNotFound unless @parent.user
82
+
83
+ @parent.user.credit_cards.find_by_prefix_id!(source_id)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ module Orders
6
+ class RefundsController < BaseController
7
+ scoped_resource :refunds
8
+
9
+ # POST /api/v3/admin/orders/:order_id/refunds
10
+ def create
11
+ with_order_lock do
12
+ payment = @parent.payments.find_by_prefix_id!(params[:payment_id])
13
+ reason = Spree::RefundReason.find_by_prefix_id!(params[:refund_reason_id]) if params[:refund_reason_id].present?
14
+ reason ||= Spree::RefundReason.first
15
+
16
+ @resource = payment.refunds.build(
17
+ amount: params[:amount],
18
+ reason: reason,
19
+ transaction_id: nil
20
+ )
21
+ authorize_resource!(@resource, :create)
22
+
23
+ if @resource.save
24
+ render json: serialize_resource(@resource), status: :created
25
+ else
26
+ render_validation_error(@resource.errors)
27
+ end
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def model_class
34
+ Spree::Refund
35
+ end
36
+
37
+ def serializer_class
38
+ Spree.api.admin_refund_serializer
39
+ end
40
+
41
+ def scope
42
+ Spree::Refund.where(payment_id: @parent.payment_ids)
43
+ end
44
+
45
+ def collection_includes
46
+ [:payment, :reason]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,59 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ module Orders
6
+ class StoreCreditsController < BaseController
7
+ scoped_resource :store_credits
8
+
9
+ skip_before_action :set_resource, raise: false
10
+
11
+ # POST /api/v3/admin/orders/:order_id/store_credits
12
+ #
13
+ # Body: { amount: Number (optional) }
14
+ #
15
+ # When `amount` is omitted, applies the customer's available store
16
+ # credit up to the order total.
17
+ def create
18
+ with_order_lock do
19
+ result = Spree.checkout_add_store_credit_service.call(
20
+ order: @parent,
21
+ amount: params[:amount].try(:to_f)
22
+ )
23
+
24
+ if result.success?
25
+ render json: serialize_resource(result.value), status: :created
26
+ else
27
+ render_service_error(result.error)
28
+ end
29
+ end
30
+ end
31
+
32
+ # DELETE /api/v3/admin/orders/:order_id/store_credits
33
+ def destroy
34
+ with_order_lock do
35
+ result = Spree.checkout_remove_store_credit_service.call(order: @parent)
36
+
37
+ if result.success?
38
+ head :no_content
39
+ else
40
+ render_service_error(result.error)
41
+ end
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ def model_class
48
+ Spree::Order
49
+ end
50
+
51
+ def serializer_class
52
+ Spree.api.admin_order_serializer
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,190 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ class OrdersController < ResourceController
6
+ include Spree::Api::V3::OrderLock
7
+
8
+ scoped_resource :orders
9
+
10
+ skip_before_action :set_resource, only: [:index, :create]
11
+ before_action :set_resource, only: [:show, :update, :destroy, :complete, :cancel, :approve, :resume, :resend_confirmation]
12
+
13
+ # POST /api/v3/admin/orders
14
+ def create
15
+ authorize!(:create, Spree::Order)
16
+
17
+ result = Spree.order_create_service.call(
18
+ store: current_store,
19
+ user: resolve_user,
20
+ params: order_create_params
21
+ )
22
+
23
+ if result.success?
24
+ @resource = result.value
25
+ render json: serialize_resource(@resource), status: :created
26
+ else
27
+ render_service_error(result.error)
28
+ end
29
+ end
30
+
31
+ # PATCH /api/v3/admin/orders/:id
32
+ def update
33
+ with_order_lock do
34
+ result = Spree.order_update_service.call(
35
+ order: @resource,
36
+ params: order_update_params
37
+ )
38
+
39
+ if result.success?
40
+ render json: serialize_resource(result.value)
41
+ else
42
+ render_validation_error(@resource.errors.presence || result.error)
43
+ end
44
+ end
45
+ end
46
+
47
+ # PATCH /api/v3/admin/orders/:id/complete
48
+ def complete
49
+ with_order_lock do
50
+ result = Spree.order_complete_service.call(
51
+ order: @resource,
52
+ payment_pending: ActiveModel::Type::Boolean.new.cast(params[:payment_pending]),
53
+ notify_customer: ActiveModel::Type::Boolean.new.cast(params[:notify_customer])
54
+ )
55
+
56
+ if result.success?
57
+ render json: serialize_resource(@resource.reload)
58
+ else
59
+ render_service_error(@resource.errors.presence || result.error, code: ERROR_CODES[:order_cannot_complete])
60
+ end
61
+ end
62
+ end
63
+
64
+ # PATCH /api/v3/admin/orders/:id/cancel
65
+ def cancel
66
+ with_order_lock do
67
+ @resource.canceled_by(try_spree_current_user)
68
+ render json: serialize_resource(@resource.reload)
69
+ end
70
+ end
71
+
72
+ # PATCH /api/v3/admin/orders/:id/approve
73
+ def approve
74
+ with_order_lock do
75
+ @resource.approved_by(try_spree_current_user)
76
+ render json: serialize_resource(@resource.reload)
77
+ end
78
+ end
79
+
80
+ # PATCH /api/v3/admin/orders/:id/resume
81
+ def resume
82
+ with_order_lock do
83
+ @resource.resume!
84
+ render json: serialize_resource(@resource.reload)
85
+ end
86
+ end
87
+
88
+ # POST /api/v3/admin/orders/:id/resend_confirmation
89
+ def resend_confirmation
90
+ @resource.publish_event('order.completed')
91
+ render json: serialize_resource(@resource)
92
+ end
93
+
94
+ protected
95
+
96
+ def model_class
97
+ Spree::Order
98
+ end
99
+
100
+ def serializer_class
101
+ Spree.api.admin_order_serializer
102
+ end
103
+
104
+ # Override scope — Order uses SingleStoreResource (for_store)
105
+ def scope
106
+ current_store.orders.accessible_by(current_ability, :show).preload_associations_lazily
107
+ end
108
+
109
+ def set_resource
110
+ @resource = scope.find_by_prefix_id!(params[:id])
111
+ @order = @resource # needed for OrderLock
112
+ authorize_resource!(@resource)
113
+ end
114
+
115
+ # Map state transition actions to :update permission
116
+ def authorize_resource!(resource = @resource, action = action_name.to_sym)
117
+ mapped_action = case action
118
+ when :complete, :cancel, :approve, :resume, :resend_confirmation
119
+ :update
120
+ else
121
+ action
122
+ end
123
+ authorize!(mapped_action, resource)
124
+ end
125
+
126
+ def collection_includes
127
+ [:line_items, :user, :channel, :rich_text_internal_note]
128
+ end
129
+
130
+ private
131
+
132
+ def resolve_user
133
+ customer_param = params[:customer_id].presence || params[:user_id].presence
134
+ return unless customer_param
135
+
136
+ Spree.user_class.find_by_param!(customer_param)
137
+ end
138
+
139
+ def order_create_params
140
+ normalize_params(
141
+ params.permit(
142
+ :email, :customer_id, :user_id, :use_customer_default_address,
143
+ :currency, :market_id, :channel_id, :locale,
144
+ :customer_note, :internal_note,
145
+ :shipping_address_id, :billing_address_id,
146
+ :preferred_stock_location_id,
147
+ :coupon_code,
148
+ metadata: {},
149
+ tags: [],
150
+ shipping_address: address_permitted_keys,
151
+ billing_address: address_permitted_keys,
152
+ items: item_permitted_keys
153
+ )
154
+ )
155
+ end
156
+
157
+ def order_update_params
158
+ normalize_params(
159
+ params.permit(
160
+ :email, :customer_id, :user_id,
161
+ :customer_note, :internal_note,
162
+ :currency, :locale, :market_id, :channel_id,
163
+ :preferred_stock_location_id,
164
+ metadata: {},
165
+ tags: [],
166
+ ship_address: address_permitted_keys,
167
+ bill_address: address_permitted_keys,
168
+ items: item_permitted_keys
169
+ )
170
+ )
171
+ end
172
+
173
+ def address_permitted_keys
174
+ [
175
+ :firstname, :lastname, :first_name, :last_name,
176
+ :address1, :address2, :city,
177
+ :country_iso, :state_abbr, :country_id, :state_id,
178
+ :zipcode, :postal_code, :phone, :alternative_phone,
179
+ :state_name, :company, :label
180
+ ]
181
+ end
182
+
183
+ def item_permitted_keys
184
+ [:variant_id, :quantity, { metadata: {} }]
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,73 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ class PaymentMethodsController < ResourceController
6
+ include Spree::Api::V3::Admin::SubclassedResource
7
+
8
+ scoped_resource :settings
9
+
10
+ subclassed_via -> { Spree::PaymentMethod.providers },
11
+ unknown_type_error: 'unknown_payment_method_type'
12
+
13
+ # Lists available payment provider subclasses for the create form.
14
+ # Returns: { data: [{ type, label, description, preference_schema }] }.
15
+ # The preference_schema array describes the provider-specific
16
+ # configuration fields, so admin UIs can render a generic
17
+ # preferences form without hard-coding per-provider knowledge.
18
+ # Filters out subclasses already installed in the current store —
19
+ # mirrors the legacy admin's "available_payment_methods" helper, so
20
+ # admins don't see (and accidentally double-install) the same
21
+ # provider twice.
22
+ def types
23
+ authorize! :create, model_class
24
+
25
+ # Query via direct join rather than `current_store.payment_methods`
26
+ # — the has_many-through association can cache stale results when
27
+ # `current_store` was loaded earlier in the request (e.g. by the
28
+ # auth layer).
29
+ installed_class_names = Spree::PaymentMethod
30
+ .joins(:store_payment_methods)
31
+ .where(spree_payment_methods_stores: { store_id: current_store.id })
32
+ .pluck(:type)
33
+ installed_shorthands = installed_class_names.filter_map do |name|
34
+ name.safe_constantize&.api_type
35
+ end
36
+ available = model_class.subclasses_with_preference_schema.reject do |entry|
37
+ installed_shorthands.include?(entry[:type])
38
+ end
39
+
40
+ render json: { data: available }
41
+ end
42
+
43
+ protected
44
+
45
+ def model_class
46
+ Spree::PaymentMethod
47
+ end
48
+
49
+ def serializer_class
50
+ Spree.api.admin_payment_method_serializer
51
+ end
52
+
53
+ # Explicit allowlist per the v3 convention — flat params, no
54
+ # reach into the global `Spree::PermittedAttributes` registry
55
+ # (which is the legacy Rails admin's surface). `type` and
56
+ # `preferences` are added by `SubclassedResource` on top.
57
+ def permitted_params
58
+ params.permit(:name, :description, :active, :storefront_visible, :auto_capture, :position, metadata: {}, preferences: {})
59
+ end
60
+
61
+ private
62
+
63
+ # New payment methods get scoped to the current store automatically.
64
+ def build_subclassed_resource(klass, attrs)
65
+ resource = klass.new(attrs)
66
+ resource.stores = [current_store] if resource.stores.empty?
67
+ resource
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end