solidus_me 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +151 -29
  3. data/app/controller/spree/admin/orders/labels_controller.rb +124 -0
  4. data/app/controller/spree/admin/solidus_me/accounts_controller.rb +7 -9
  5. data/app/models/solidus_me/account.rb +89 -43
  6. data/app/models/solidus_me/label.rb +155 -0
  7. data/app/models/solidus_me/me_client.rb +14 -0
  8. data/app/models/solidus_me/melhor_envio.rb +18 -140
  9. data/app/models/solidus_me/package_estimator.rb +22 -0
  10. data/app/models/solidus_me/rate_calculator.rb +37 -0
  11. data/app/views/spree/admin/orders/labels/_steps.html.erb +78 -0
  12. data/app/views/spree/admin/orders/labels/index.html.erb +28 -0
  13. data/app/views/spree/admin/orders/labels/show.html.erb +46 -0
  14. data/app/views/spree/admin/orders/labels/step_generate.html.erb +48 -0
  15. data/app/views/spree/admin/orders/labels/step_payment.html.erb +83 -0
  16. data/app/views/spree/admin/orders/labels/step_print.html.erb +48 -0
  17. data/app/views/spree/admin/solidus_me/accounts/_form.html.erb +14 -3
  18. data/app/views/spree/admin/solidus_me/accounts/index.html.erb +11 -6
  19. data/config/routes.rb +9 -0
  20. data/db/migrate/20250801130502_rename_column_token_expires_in.rb +5 -0
  21. data/db/migrate/20250801130700_add_column_test_mode.rb +5 -0
  22. data/db/migrate/20250801142036_create_solidus_me_labels.rb +16 -0
  23. data/db/migrate/20250801170700_add_last_synced_at_to_solidus_me_accounts.rb +5 -0
  24. data/db/migrate/20250801174106_add_timestamps_to_labels.rb +6 -0
  25. data/lib/generators/solidus_me/install/install_generator.rb +4 -0
  26. data/lib/generators/solidus_me/install/templates/app/overrides/spree_order_solidus_me_labels.rb +9 -0
  27. data/lib/solidus_me/version.rb +1 -1
  28. metadata +35 -4
@@ -0,0 +1,14 @@
1
+ module SolidusMe
2
+ class MeClient
3
+ def self.for_account(account)
4
+ MeApi::Client.new(
5
+ access_token: account.access_token,
6
+ refresh_token: account.refresh_token,
7
+ client_id: account.client_id,
8
+ client_secret: account.client_secret,
9
+ token_expires_at: account.token_expires_at,
10
+ test_mode: account.test_mode
11
+ )
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,4 @@
1
1
  module SolidusMe
2
-
3
2
  class MelhorEnvio
4
3
  SHIPPING_METHODS = [
5
4
  { name: "Correios PAC", carrier: "Correios", service_level: "PAC", external_id: 1 },
@@ -13,7 +12,7 @@ module SolidusMe
13
12
  def initialize(package, shipping_methods)
14
13
  @package = package
15
14
  @shipping_methods = shipping_methods
16
- @me_account = Account.first
15
+ @me_account = me_account || set_default_me_account
17
16
  end
18
17
 
19
18
  def self.generate_code external_id
@@ -36,164 +35,43 @@ module SolidusMe
36
35
  end
37
36
 
38
37
  def shipping_rates
39
- melhor_envio_rates = get_rates_from_melhor_envio(package)
40
- shipping_rates = melhor_envio_rates.map do |melhor_envio_rate|
41
- build_shipping_rate(melhor_envio_rate, package)
38
+ melhor_envio_rates = RateCalculator.new(package, me_account).calculate
39
+ valid_melhor_envio_rates = select_valid_shipping_rates(melhor_envio_rates)
40
+ shipping_rates = valid_melhor_envio_rates.map do |melhor_envio_rate|
41
+ build_shipping_rate(melhor_envio_rate)
42
42
  end.compact
43
43
 
44
44
  shipping_rates
45
45
  end
46
46
 
47
- def insert_package_into_cart from
48
- me_client = set_me_client
49
- order = package.order
50
- line_items = order.line_items
51
- address = order.ship_address
52
- dimensions = estimate_dimensions_from_package(package)
53
- tax_id_from = cleaned_tax_id(@me_account.tax_id)
54
- tax_id_to = cleaned_tax_id(order.tax_id)
55
-
56
- shipping_method_name = @shipping_methods.sole.name
57
- service_id = SHIPPING_METHODS.filter { _1[:name] == shipping_method_name }.sole[:external_id]
58
-
59
- from = {
60
- name: @me_account.name,
61
- phone: @me_account.phone,
62
- email: @me_account.email,
63
- address: @me_account.address,
64
- complement: @me_account.address_complement,
65
- number: @me_account.address_number,
66
- district: @me_account.address_district,
67
- city: @me_account.address_city,
68
- postal_code: @me_account.postal_code_from,
69
- state_abbr: @me_account.address_state_abbr
70
- }
71
-
72
- document_key = tax_id_from.length == 11 ? :document : :company_document
73
- from[document_key] = tax_id_from
74
-
75
- to = {
76
- name: address.name,
77
- phone: address.phone,
78
- email: order.email,
79
- address: address.address1,
80
- complement: address.address2,
81
- number: address.number,
82
- district: address.district,
83
- city: address.city,
84
- postal_code: address.zipcode,
85
- state_abbr: address.state.abbr
86
- }
87
-
88
- document_key = tax_id_to.length == 11 ? :document : :company_document
89
- to[document_key] = tax_id_to
90
-
91
- products = line_items.map do |line_item|
92
- {
93
- name: line_item.name,
94
- quantity: line_item.quantity,
95
- unitary_value: line_item.price
96
- }
97
- end
98
-
99
- volumes = [{
100
- width: dimensions[0].to_i.round(2),
101
- height: dimensions[1].to_i.round(2),
102
- length: dimensions[2].to_i.round(2),
103
- weight: package.weight / 1000
104
- }]
105
-
106
- options = {
107
- insurance_value: order.total,
108
- }
109
-
110
- me_client.insert_package_into_cart(service_id:, from:, to:, products:, volumes:, options:)
111
- end
112
-
113
- def payment_ticket(ticket_id)
114
- me_client = set_me_client
115
- me_client.payment_ticket ticket_id
116
- end
117
-
118
- def generate_ticket(ticket_id)
119
- me_client = set_me_client
120
- me_client.generate_ticket ticket_id
121
- end
122
-
123
- def print_ticket(ticket_id)
124
- me_client = set_me_client
125
- me_client.print_ticket ticket_id
126
- end
127
-
128
- def get_ticket(ticket_id)
129
- me_client = set_me_client
130
- me_client.get_ticket ticket_id
131
- end
132
-
133
47
  private
134
48
 
135
- def set_me_client
136
- return if @me_account.blank?
137
-
138
- @me_account.check_token
139
- MeApi::Client.new(@me_account.access_token)
140
- end
141
-
142
- def cleaned_tax_id(tax_id)
143
- tax_id.gsub(/\D/, "")
49
+ def set_default_me_account
50
+ @default_me_account ||= Rails.env.production? ? SolidusMe::Account.first_active_production_mode : SolidusMe::Account.first_active_test_mode
144
51
  end
145
52
 
146
- def get_rates_from_melhor_envio(package)
147
- me_client = set_me_client
148
- price = package.contents.map { |content| content.price }.sum
149
- weight = package.weight
150
- dimensions = estimate_dimensions_from_package(package)
151
-
152
- zipcode = package.order.ship_address.zipcode
53
+ def select_valid_shipping_rates(melhor_envio_rates)
153
54
  available_shipping_method_codes = @shipping_methods.pluck(:code)
154
- rates = me_client.rates(
155
- from: @me_account.postal_code_from,
156
- to: zipcode,
157
- weight_kg: (weight / 1000.00),
158
- width_cm: dimensions[0].to_i.round(2),
159
- height_cm: dimensions[1].to_i.round(2),
160
- length_cm: dimensions[2].to_i.round(2),
161
- contents_value_brl: price
162
- )
163
- rates.select { |rate| rate.price > 0 && available_shipping_method_codes.include?(self.class.generate_code(rate.id)) }
164
- rescue
165
- []
166
- end
167
-
168
- def estimate_dimensions_from_package(package)
169
- volume = package.contents.sum { |item| dimensions_from_item(item).reduce(:*) }
170
- cube_side = Math.cbrt(volume)
171
- [cube_side, cube_side, cube_side]
55
+ melhor_envio_rates.select { |rate| rate.try(:price) && rate.price&.to_f > 0 && available_shipping_method_codes.include?(self.class.generate_code(rate.id)) }
172
56
  end
173
57
 
174
- def dimensions_from_item(item)
175
- width = item.variant.width || 20
176
- height = item.variant.height || 10
177
- depth = item.variant.depth || 10
178
- [width, height, depth]
179
- end
180
-
181
- def build_shipping_rate(melhor_envio_rate, package)
182
- return unless melhor_envio_rate.max_delivery_time
58
+ def build_shipping_rate(melhor_envio_rate)
59
+ return unless melhor_envio_rate.delivery_range.max.to_s
183
60
  shipping_method = Spree::ShippingMethod.find_by(code: self.class.generate_code(melhor_envio_rate.id))
184
- estimate_prose = "Chegará em até #{ActionController::Base.helpers.pluralize(melhor_envio_rate.max_delivery_time, "dia", plural: "dias")} úteis"
185
61
 
186
62
  Spree::ShippingRate.new(
187
63
  shipment: package.shipment,
188
64
  shipping_method: shipping_method,
189
- cost: melhor_envio_rate.price,
190
- min_delivery_time: melhor_envio_rate.min_delivery_time.business_days.from_now,
191
- max_delivery_time: melhor_envio_rate.max_delivery_time.business_days.from_now,
192
- estimate_prose: estimate_prose,
65
+ cost: melhor_envio_rate.price&.to_f,
66
+ min_delivery_time: melhor_envio_rate.delivery_range.min.business_days.from_now,
67
+ max_delivery_time: melhor_envio_rate.delivery_range.max.business_days.from_now,
68
+ estimate_prose: estimate_prose(melhor_envio_rate),
193
69
  expires_in: Time.now + 2.hours
194
70
  )
195
71
  end
196
72
 
73
+ def estimate_prose(melhor_envio_rate)
74
+ "Chegará em até #{ActionController::Base.helpers.pluralize(melhor_envio_rate.delivery_range.max.to_s, "dia", plural: "dias")} úteis"
75
+ end
197
76
  end
198
-
199
77
  end
@@ -0,0 +1,22 @@
1
+ module SolidusMe
2
+ class PackageEstimator
3
+ attr_reader :package
4
+
5
+ def initialize(package)
6
+ @package = package
7
+ end
8
+
9
+ def estimate_dimensions
10
+ volume = package.contents.sum { |item| dimensions_from_item(item).reduce(:*) }
11
+ cube_side = Math.cbrt(volume)
12
+ [cube_side, cube_side, cube_side]
13
+ end
14
+
15
+ def dimensions_from_item(item)
16
+ width = (item.variant.width.to_i || 20).round(2)
17
+ height = (item.variant.height.to_i || 10).round(2)
18
+ depth = (item.variant.depth.to_i || 10).round(2)
19
+ [width, height, depth]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module SolidusMe
2
+ class RateCalculator
3
+ attr_reader :package, :me_account
4
+ def initialize(package, me_account)
5
+ @package = package
6
+ raise "No Melhor Envio account found" unless me_account
7
+ @me_account = me_account
8
+ end
9
+
10
+ def calculate
11
+ price = package.contents.map { |content| content.price }.sum
12
+ weight = package.weight
13
+ dimensions = SolidusMe::PackageEstimator.new(package).estimate_dimensions
14
+ zipcode = package.order.ship_address.zipcode
15
+ rates = me_client.rates(
16
+ from_postal_code: me_account.postal_code_from,
17
+ to_postal_code: zipcode,
18
+ volume: MeApi::Volume.new(
19
+ width_cm: dimensions[0],
20
+ height_cm: dimensions[1],
21
+ length_cm: dimensions[2],
22
+ weight_kg: (weight / 1000.00)
23
+ ),
24
+ contents_value_brl: price
25
+ )
26
+ rates
27
+ rescue
28
+ []
29
+ end
30
+
31
+ private
32
+
33
+ def me_client
34
+ @me_client ||= MeClient.for_account(me_account)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,78 @@
1
+ <div style="display: flex; justify-content: center; width: 100%; margin: 20px 0;">
2
+ <div style="background: white; padding: 20px 25px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); border: 1px solid #e9ecef; max-width: 600px; width: 100%;">
3
+
4
+ <div style="display: flex; justify-content: space-between; align-items: center; position: relative;">
5
+
6
+ <div style="display: flex; flex-direction: column; align-items: center; z-index: 3; position: relative;">
7
+ <div style="width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px;
8
+ background: <%= current_step > 1 ? 'linear-gradient(135deg, #28a745, #20c997)' : (current_step == 1 ? '#007bff' : '#e9ecef') %>;
9
+ color: <%= current_step >= 1 ? 'white' : '#6c757d' %>;
10
+ box-shadow: <%= current_step > 1 ? '0 2px 6px rgba(40, 167, 69, 0.3)' : (current_step == 1 ? '0 2px 6px rgba(0, 123, 255, 0.3)' : '0 1px 3px rgba(0,0,0,0.1)') %>;
11
+ border: 2px solid <%= current_step > 1 ? '#28a745' : (current_step == 1 ? '#007bff' : '#dee2e6') %>;
12
+ transition: all 0.3s ease;">
13
+ <%= current_step > 1 ? '✓' : '1' %>
14
+ </div>
15
+ <small style="margin-top: 6px; font-weight: 500; color: <%= current_step > 1 ? '#28a745' : (current_step == 1 ? '#007bff' : '#6c757d') %>; font-size: 9px; text-align: center; line-height: 1.1; max-width: 50px;">
16
+ Adicionar<br>Carrinho
17
+ </small>
18
+ </div>
19
+
20
+ <div style="display: flex; flex-direction: column; align-items: center; z-index: 3; position: relative;">
21
+ <div style="width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px;
22
+ background: <%= current_step > 2 ? 'linear-gradient(135deg, #28a745, #20c997)' : (current_step == 2 ? '#007bff' : '#e9ecef') %>;
23
+ color: <%= current_step >= 2 ? 'white' : '#6c757d' %>;
24
+ box-shadow: <%= current_step > 2 ? '0 2px 6px rgba(40, 167, 69, 0.3)' : (current_step == 2 ? '0 2px 6px rgba(0, 123, 255, 0.3)' : '0 1px 3px rgba(0,0,0,0.1)') %>;
25
+ border: 2px solid <%= current_step > 2 ? '#28a745' : (current_step == 2 ? '#007bff' : '#dee2e6') %>;
26
+ transition: all 0.3s ease;">
27
+ <%= current_step > 2 ? '✓' : '2' %>
28
+ </div>
29
+ <small style="margin-top: 6px; font-weight: 500; color: <%= current_step > 2 ? '#28a745' : (current_step == 2 ? '#007bff' : '#6c757d') %>; font-size: 9px; text-align: center; line-height: 1.1; max-width: 50px;">
30
+ Pagar<br>Etiqueta
31
+ </small>
32
+ </div>
33
+
34
+ <div style="display: flex; flex-direction: column; align-items: center; z-index: 3; position: relative;">
35
+ <div style="width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px;
36
+ background: <%= current_step > 3 ? 'linear-gradient(135deg, #28a745, #20c997)' : (current_step == 3 ? '#007bff' : '#e9ecef') %>;
37
+ color: <%= current_step >= 3 ? 'white' : '#6c757d' %>;
38
+ box-shadow: <%= current_step > 3 ? '0 2px 6px rgba(40, 167, 69, 0.3)' : (current_step == 3 ? '0 2px 6px rgba(0, 123, 255, 0.3)' : '0 1px 3px rgba(0,0,0,0.1)') %>;
39
+ border: 2px solid <%= current_step > 3 ? '#28a745' : (current_step == 3 ? '#007bff' : '#dee2e6') %>;
40
+ transition: all 0.3s ease;">
41
+ <%= current_step > 3 ? '✓' : '3' %>
42
+ </div>
43
+ <small style="margin-top: 6px; font-weight: 500; color: <%= current_step > 3 ? '#28a745' : (current_step == 3 ? '#007bff' : '#6c757d') %>; font-size: 9px; text-align: center; line-height: 1.1; max-width: 50px;">
44
+ Gerar<br>Etiqueta
45
+ </small>
46
+ </div>
47
+
48
+ <div style="display: flex; flex-direction: column; align-items: center; z-index: 3; position: relative;">
49
+ <div style="width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px;
50
+ background: <%= current_step > 4 ? 'linear-gradient(135deg, #28a745, #20c997)' : (current_step == 4 ? '#007bff' : '#e9ecef') %>;
51
+ color: <%= current_step >= 4 ? 'white' : '#6c757d' %>;
52
+ box-shadow: <%= current_step > 4 ? '0 2px 6px rgba(40, 167, 69, 0.3)' : (current_step == 4 ? '0 2px 6px rgba(0, 123, 255, 0.3)' : '0 1px 3px rgba(0,0,0,0.1)') %>;
53
+ border: 2px solid <%= current_step > 4 ? '#28a745' : (current_step == 4 ? '#007bff' : '#dee2e6') %>;
54
+ transition: all 0.3s ease;">
55
+ <%= current_step > 4 ? '✓' : '4' %>
56
+ </div>
57
+ <small style="margin-top: 6px; font-weight: 500; color: <%= current_step > 4 ? '#28a745' : (current_step == 4 ? '#007bff' : '#6c757d') %>; font-size: 9px; text-align: center; line-height: 1.1; max-width: 50px;">
58
+ Gerar<br>Link
59
+ </small>
60
+ </div>
61
+
62
+ <div style="display: flex; flex-direction: column; align-items: center; z-index: 3; position: relative;">
63
+ <div style="width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px;
64
+ background: <%= current_step >= 5 ? 'linear-gradient(135deg, #28a745, #20c997)' : '#e9ecef' %>;
65
+ color: <%= current_step >= 5 ? 'white' : '#6c757d' %>;
66
+ box-shadow: <%= current_step >= 5 ? '0 2px 6px rgba(40, 167, 69, 0.3)' : '0 1px 3px rgba(0,0,0,0.1)' %>;
67
+ border: 2px solid <%= current_step >= 5 ? '#28a745' : '#dee2e6' %>;
68
+ transition: all 0.3s ease;">
69
+ <%= current_step >= 5 ? '✓' : '5' %>
70
+ </div>
71
+ <small style="margin-top: 6px; font-weight: 500; color: <%= current_step >= 5 ? '#28a745' : '#6c757d' %>; font-size: 9px; text-align: center; line-height: 1.1; max-width: 50px;">
72
+ Etiqueta<br>Finalizada
73
+ </small>
74
+ </div>
75
+
76
+ </div>
77
+ </div>
78
+ </div>
@@ -0,0 +1,28 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Etiquetas' } %>
2
+
3
+ <% admin_breadcrumb("Etiquetas") %>
4
+
5
+ <div data-order-number="<%= @order.number %>" style="text-align: center !important; display: flex; flex-direction: column; align-items: center; width: 100%;">
6
+ <div style="width: 100%; max-width: 800px;">
7
+ <%= render partial: 'steps', locals: { current_step: @current_step } %>
8
+
9
+ <fieldset class="no-border-bottom" style="text-align: center !important;">
10
+ <legend style="text-align: center !important; width: 100%; margin: 0 auto;">Passo 1: Adicionar ao Carrinho do Melhor Envio</legend>
11
+
12
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
13
+ <p style="text-align: center !important; margin: 0 auto;">Para começar, vamos adicionar os itens deste pedido ao carrinho do Melhor Envio. Isso irá calcular o valor do frete e preparar a etiqueta.</p>
14
+ </div>
15
+
16
+ <div style="text-align: center !important; width: 100%; display: flex; justify-content: center;">
17
+ <%= form_with url: admin_orders_create_label_url do |form| %>
18
+ <%= form.hidden_field :order_number, value: @order.number %>
19
+ <div class="form-buttons filter-actions actions" data-hook="buttons" style="text-align: center !important;">
20
+ <%= form.submit "Adicionar ao Carrinho do Melhor Envio", class: "btn btn-primary mt-2", style: "margin: 0 auto; display: block;" %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ </fieldset>
25
+ </div>
26
+
27
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @order } %>
28
+ </div>
@@ -0,0 +1,46 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Etiqueta' } %>
2
+
3
+ <% admin_breadcrumb("Etiqueta") %>
4
+
5
+ <div data-order-number="<%= @order.number %>" style="text-align: center !important; display: flex; flex-direction: column; align-items: center; width: 100%;">
6
+ <div style="width: 100%; max-width: 800px;">
7
+ <%= render partial: 'steps', locals: { current_step: @current_step } %>
8
+
9
+ <fieldset class="no-border-bottom" style="text-align: center !important;">
10
+ <legend style="text-align: center !important; width: 100%; margin: 0 auto;">Etiqueta Finalizada</legend>
11
+
12
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
13
+ <p style="text-align: center !important; margin: 0 auto;">Sua etiqueta está pronta para impressão e o código de rastreio foi gerado.</p>
14
+ </div>
15
+
16
+ <div style="display: flex; justify-content: center; margin-bottom: 20px; width: 100%;">
17
+ <div style="display: flex; gap: 40px; align-items: center; justify-content: center;">
18
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
19
+ <label style="text-align: center;">Valor pago pelo cliente:</label>
20
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.order.shipment_total %></h3>
21
+ </div>
22
+
23
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
24
+ <label style="text-align: center;">Preço da etiqueta:</label>
25
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.price %></h3>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <% if @label.link %>
31
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
32
+ <%= link_to "📄 Abrir Etiqueta para Impressão", @label.link, target: "_blank", class: "button", style: "margin: 0 auto; display: inline-block;" %>
33
+ </div>
34
+ <% end %>
35
+
36
+ <% if @label.tracking_code %>
37
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
38
+ <label style="text-align: center !important;">Código de rastreio:</label>
39
+ <p style="text-align: center !important; margin: 5px 0;"><strong style="font-size: 16px; color: #007bff;"><%= @label.tracking_code %></strong></p>
40
+ </div>
41
+ <% end %>
42
+ </fieldset>
43
+ </div>
44
+
45
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @order } %>
46
+ </div>
@@ -0,0 +1,48 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Etiquetas' } %>
2
+
3
+ <% admin_breadcrumb("Etiquetas") %>
4
+
5
+ <div data-order-number="<%= @order.number %>" style="text-align: center !important; display: flex; flex-direction: column; align-items: center; width: 100%;">
6
+ <div style="width: 100%; max-width: 800px;">
7
+ <%= render partial: 'steps', locals: { current_step: @current_step } %>
8
+
9
+ <fieldset class="no-border-bottom" style="text-align: center !important;">
10
+ <legend style="text-align: center !important; width: 100%; margin: 0 auto;">Passo 3: Gerar Etiqueta</legend>
11
+
12
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
13
+ <p style="text-align: center !important; margin: 0 auto;">O pagamento foi confirmado. Agora vamos gerar a etiqueta de envio no sistema do Melhor Envio.</p>
14
+ </div>
15
+
16
+ <div style="display: flex; justify-content: center; margin-bottom: 20px; width: 100%;">
17
+ <div style="display: flex; gap: 40px; align-items: center; justify-content: center;">
18
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
19
+ <label style="text-align: center;">Valor pago pelo cliente:</label>
20
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.order.shipment_total %></h3>
21
+ </div>
22
+
23
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
24
+ <label style="text-align: center;">Preço da etiqueta:</label>
25
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.price %></h3>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
31
+ <label style="text-align: center !important;">Status do Pagamento:</label>
32
+ <p style="color: #28a745; font-weight: bold; text-align: center !important; margin: 5px 0;">✓ PAGO</p>
33
+ </div>
34
+
35
+ <hr style="margin: 20px auto; width: 80%; border: 1px solid #dee2e6;">
36
+
37
+ <div style="text-align: center !important; width: 100%; display: flex; justify-content: center;">
38
+ <%= form_with url: admin_orders_update_label_url(@order.number, @label.id), method: :patch, local: true do |form| %>
39
+ <div class="form-buttons filter-actions actions" data-hook="buttons" style="text-align: center !important;">
40
+ <%= form.submit "Gerar Etiqueta", class: "btn btn-primary", style: "margin: 0 auto; display: block;" %>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </fieldset>
45
+ </div>
46
+
47
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @order } %>
48
+ </div>
@@ -0,0 +1,83 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Etiquetas' } %>
2
+
3
+ <% admin_breadcrumb("Etiquetas") %>
4
+
5
+ <div data-order-number="<%= @order.number %>" style="text-align: center !important; display: flex; flex-direction: column; align-items: center; width: 100%;">
6
+ <div style="width: 100%; max-width: 800px;">
7
+ <%= render partial: 'steps', locals: { current_step: @current_step } %>
8
+
9
+ <fieldset class="no-border-bottom" style="text-align: center !important;">
10
+ <legend style="text-align: center !important; width: 100%; margin: 0 auto;">Passo 2: Pagar Etiqueta</legend>
11
+
12
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
13
+ <p style="text-align: center !important; margin: 0 auto;">Os itens foram adicionados ao carrinho com sucesso! Agora vamos efetuar o pagamento da etiqueta no Melhor Envio.</p>
14
+ </div>
15
+
16
+ <div style="display: flex; justify-content: center; margin-bottom: 25px; width: 100%;">
17
+ <div style="display: flex; gap: 60px; align-items: center; justify-content: center;">
18
+ <div style="text-align: center; padding: 16px; border-radius: 6px; background: #fafbfc; border: 1px solid #e8eaed;">
19
+ <div style="color: #5f6368; font-size: 12px; font-weight: 500; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.3px;">
20
+ Valor pago pelo cliente
21
+ </div>
22
+ <div style="color: #202124; font-size: 18px; font-weight: 600;">
23
+ <%= number_to_currency @label.order.shipment_total %>
24
+ </div>
25
+ </div>
26
+
27
+ <div style="text-align: center; padding: 16px; border-radius: 6px; background: #fafbfc; border: 1px solid #e8eaed;">
28
+ <div style="color: #5f6368; font-size: 12px; font-weight: 500; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.3px;">
29
+ Preço da etiqueta
30
+ </div>
31
+ <div style="color: #202124; font-size: 18px; font-weight: 600;">
32
+ <%= number_to_currency @label.price %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+
38
+ <div style="margin-bottom: 25px; text-align: center !important; width: 100%;">
39
+ <% if @balance < @label.price %>
40
+ <div style="background: #f8f9fa; border: 1px solid #ff6b6b; border-radius: 6px; padding: 18px; margin: 0 auto; max-width: 320px;">
41
+ <div style="color: #dc3545; font-size: 12px; font-weight: 500; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.3px;">
42
+ Saldo Melhor Envio
43
+ </div>
44
+ <div style="font-weight: 600; color: #dc3545; font-size: 20px; margin-bottom: 12px;">
45
+ <%= number_to_currency @balance %>
46
+ </div>
47
+ <div style="color: #6c757d; font-size: 13px; margin-bottom: 12px;">
48
+ Saldo insuficiente para pagar a etiqueta
49
+ </div>
50
+ <a href="https://app.melhorenvio.com.br/melhor-carteira" target="_blank" style="display: inline-block; background: #007bff; color: white; text-decoration: none; padding: 8px 16px; border-radius: 4px; font-size: 13px; font-weight: 500;">
51
+ Adicionar Saldo no Melhor Envio
52
+ </a>
53
+ </div>
54
+ <% else %>
55
+ <div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 18px; margin: 0 auto; max-width: 280px;">
56
+ <div style="color: #5f6368; font-size: 12px; font-weight: 500; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.3px;">
57
+ Saldo Melhor Envio
58
+ </div>
59
+ <div style="font-weight: 600; color: #1a7332; font-size: 20px;">
60
+ <%= number_to_currency @balance %>
61
+ </div>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+
66
+ <hr style="margin: 20px auto; width: 80%; border: 1px solid #dee2e6;">
67
+
68
+ <div style="text-align: center !important; width: 100%; display: flex; justify-content: center;">
69
+ <%= form_with url: admin_orders_update_label_url(@order.number, @label.id), method: :patch, local: true do |form| %>
70
+ <div class="form-buttons filter-actions actions" data-hook="buttons" style="text-align: center !important;">
71
+ <% if @balance < @label.price %>
72
+ <%= form.submit "Saldo Insuficiente", class: "btn btn-secondary", disabled: true, style: "margin: 0 auto; display: block;" %>
73
+ <% else %>
74
+ <%= form.submit "Pagar Etiqueta", class: "btn btn-primary", style: "margin: 0 auto; display: block;" %>
75
+ <% end %>
76
+ </div>
77
+ <% end %>
78
+ </div>
79
+ </fieldset>
80
+ </div>
81
+
82
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @order } %>
83
+ </div>
@@ -0,0 +1,48 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Etiquetas' } %>
2
+
3
+ <% admin_breadcrumb("Etiquetas") %>
4
+
5
+ <div data-order-number="<%= @order.number %>" style="text-align: center !important; display: flex; flex-direction: column; align-items: center; width: 100%;">
6
+ <div style="width: 100%; max-width: 800px;">
7
+ <%= render partial: 'steps', locals: { current_step: @current_step } %>
8
+
9
+ <fieldset class="no-border-bottom" style="text-align: center !important;">
10
+ <legend style="text-align: center !important; width: 100%; margin: 0 auto;">Passo 4: Gerar Link de Impressão</legend>
11
+
12
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
13
+ <p style="text-align: center !important; margin: 0 auto;">A etiqueta foi gerada com sucesso. Agora vamos gerar o link para impressão da etiqueta.</p>
14
+ </div>
15
+
16
+ <div style="display: flex; justify-content: center; margin-bottom: 20px; width: 100%;">
17
+ <div style="display: flex; gap: 40px; align-items: center; justify-content: center;">
18
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
19
+ <label style="text-align: center;">Valor pago pelo cliente:</label>
20
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.order.shipment_total %></h3>
21
+ </div>
22
+
23
+ <div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
24
+ <label style="text-align: center;">Preço da etiqueta:</label>
25
+ <h3 style="text-align: center; margin: 5px 0;"><%= number_to_currency @label.price %></h3>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div style="margin-bottom: 20px; text-align: center !important; width: 100%;">
31
+ <label style="text-align: center !important;">Status da Etiqueta:</label>
32
+ <p style="color: #28a745; font-weight: bold; text-align: center !important; margin: 5px 0;">✓ ETIQUETA GERADA</p>
33
+ </div>
34
+
35
+ <hr style="margin: 20px auto; width: 80%; border: 1px solid #dee2e6;">
36
+
37
+ <div style="text-align: center !important; width: 100%; display: flex; justify-content: center;">
38
+ <%= form_with url: admin_orders_update_label_url(@order.number, @label.id), method: :patch, local: true do |form| %>
39
+ <div class="form-buttons filter-actions actions" data-hook="buttons" style="text-align: center !important;">
40
+ <%= form.submit "Gerar Link de Impressão", class: "btn btn-primary", style: "margin: 0 auto; display: block;" %>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </fieldset>
45
+ </div>
46
+
47
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @order } %>
48
+ </div>
@@ -128,6 +128,17 @@
128
128
  <%= form.error_message_on :client_secret %>
129
129
  <% end %>
130
130
 
131
+ <%= form.field_container :test_mode do %>
132
+ <div style="display: flex; align-items: center; gap: 10px;">
133
+ <%= form.check_box :test_mode %>
134
+ <%= form.label :test_mode, "Conta Teste", style: "margin: 0; font-weight: normal;" %>
135
+ </div>
136
+ <%= form.error_message_on :test_mode %>
137
+ <p class="field-description" style="margin-top: 5px; color: #666; font-size: 0.9em;">
138
+ Marque esta opção para usar a API em modo de teste
139
+ </p>
140
+ <% end %>
141
+
131
142
  </div>
132
143
 
133
144
  <div class="col-12 col-md-6">
@@ -157,11 +168,11 @@
157
168
  <% end %>
158
169
 
159
170
  <div style="margin-top: 10px;">
160
- <%= form.hidden_field :token_expires_in %>
171
+ <%= form.hidden_field :token_expires_at %>
161
172
  <% if params["action"] != 'new' %>
162
173
  <div class="field">
163
- <%= label_tag :token_expires_in, "Token Expiration" %>
164
- <div class="fullwidth" id="token_expires_in" name="token_expires_in"><%= me_account.token_expires_in %> </div>
174
+ <%= label_tag :token_expires_at, "Token Expiration" %>
175
+ <div class="fullwidth" id="token_expires_at" name="token_expires_at"><%= me_account.token_expires_at %> </div>
165
176
  </div>
166
177
  <% end %>
167
178
  </div>