spree_razorpay_checkout 0.1.2 → 0.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce91b0aa0a699af98af1026d7d4ee7273d42f60264fb2a437d6148fd2a5b5aae
4
- data.tar.gz: a124ad28f283318d9e11f9b5c106391ca27fa84a63fcac0059c53c72fc559a6d
3
+ metadata.gz: 9fa91a572f8ba303d47dd90b0142eff75dc21478b85c9d85a9d2ca842318a4b9
4
+ data.tar.gz: 889b39cf1a9076a0b88d6d9a08d06971d4890f9fb08dbd94b4e9ba170f2d92a2
5
5
  SHA512:
6
- metadata.gz: 5134c27e23b733c35611c670bcff05ca88fc706fe38372a5d45f40ff5e9fce4c0491420eb9c11971ef9f92aa5eb30d89c6c137a92fc3060b183a3c2698e57474
7
- data.tar.gz: 6d18efe3da267fc36c039dcdf4975b91fe2bff36f88178726e2f108e01bd9c7a7e9d7d6a2910b237ca14ee043129843ed7d2d5c22adf06756c27c893def5102c
6
+ metadata.gz: f11e14acd2e4325710adc68337f10fd3d8ab3736dc5afb9d1dc91d91af5991ac3e9feaf296f337bad37ff5f469d2d4778c9dc33ecfac8314daf95766ebb9684e
7
+ data.tar.gz: 21c4b3840513e8a19987a9901d5afe1d32f18832869e876c0bc03098165ab703a55a95e101a2092403458c4e1a5314de46d2474ea786c8b69e14a3fb190ceb91
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
3
+ <!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
4
+ <defs>
5
+ <style>
6
+ .st0 {
7
+ fill: none;
8
+ }
9
+
10
+ .st1 {
11
+ fill-rule: evenodd;
12
+ }
13
+ </style>
14
+ </defs>
15
+ <path class="st0" d="M0,0h24v24H0V0Z"/>
16
+ <g>
17
+ <path id="Fill-17" class="st1" d="M11,9.2l-.8,2.8,4.5-2.8-2.8,10.8h2.9l4.3-15.9-8,5.2Z"/>
18
+ <path id="Fill-19" class="st1" d="M6.3,15.4l-1.3,4.5h6l2.4-9.3-7.1,4.7Z"/>
19
+ </g>
20
+ </svg>
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module ProductsControllerDecorator
3
+ def self.prepended(base)
4
+ base.before_action :force_razorpay_view_priority
5
+ end
6
+
7
+ private
8
+
9
+ def force_razorpay_view_priority
10
+ plugin_theme_path = SpreeRazorpayCheckout::Engine.root.join('app', 'views', 'themes', 'default')
11
+ prepend_view_path(plugin_theme_path)
12
+
13
+ end
14
+ end
15
+ end
16
+
17
+ ::Spree::ProductsController.prepend(Spree::ProductsControllerDecorator)
@@ -0,0 +1,79 @@
1
+ module Spree
2
+ module PageBlocks
3
+ module Products
4
+ class RazorpayAffordability < Spree::PageBlock
5
+ # Key / fallback
6
+ preference :merchant_key_id, :string
7
+ preference :fallback_amount, :integer, default: 50000
8
+
9
+ # Appearance & Color (header/theme + heading + content + discount + link/button + footer + darkmode)
10
+ preference :theme_color, :string, default: '#800080' # header theme color
11
+ preference :heading_color, :string, default: '#000000'
12
+ preference :heading_font_size, :integer, default: 14 # px
13
+
14
+ preference :content_background_color, :string, default: '#ffffff'
15
+ preference :content_color, :string, default: '#000000'
16
+ preference :content_font_size, :integer, default: 13 # px
17
+
18
+ preference :discount_color, :string, default: '#e60099'
19
+
20
+ # link/button
21
+ preference :link_button, :boolean, default: true
22
+ preference :link_color, :string, default: '#000000'
23
+ preference :link_font_size, :integer, default: 12 # px
24
+
25
+ # footer
26
+ preference :footer_color, :string, default: '#000000'
27
+ preference :footer_font_size, :integer, default: 12 # px
28
+ preference :footer_dark_logo, :boolean, default: false
29
+
30
+ # dark mode
31
+ preference :is_dark_mode, :boolean, default: false
32
+
33
+ # Widget settings toggles
34
+ preference :offers_enabled, :boolean, default: true
35
+ preference :emi_enabled, :boolean, default: true
36
+ preference :cardless_emi_enabled, :boolean, default: true
37
+ preference :paylater_enabled, :boolean, default: true
38
+
39
+ # expose names & metadata for admin (optional helper methods)
40
+ def self.block_name
41
+ "Razorpay Affordability Widget"
42
+ end
43
+
44
+ def self.display_name
45
+ "Razorpay Affordability"
46
+ end
47
+
48
+ def icon_name
49
+ "hexagon-letter-r"
50
+ end
51
+
52
+ def render(view_context, locals = {})
53
+ Rails.logger.info "🎯 RazorpayAffordability#render called for block ID: #{id}"
54
+ if respond_to?(:available?, true)
55
+ is_available = available?(locals)
56
+ Rails.logger.info " Available check: #{is_available}"
57
+ unless is_available
58
+ Rails.logger.warn " ⚠️ Block marked as not available, but rendering anyway"
59
+ end
60
+ end
61
+
62
+ begin
63
+ Rails.logger.info " Rendering partial: spree/page_blocks/products/razorpay_affordability/razorpay_affordability"
64
+ result = view_context.render partial: 'spree/page_blocks/products/razorpay_affordability/razorpay_affordability',
65
+ locals: locals.merge(block: self, page_block: self)
66
+ Rails.logger.info " ✅ Render successful, output length: #{result.to_s.length}"
67
+ result
68
+ rescue ActionView::MissingTemplate => e
69
+ Rails.logger.error " ❌ Missing template: #{e.message}"
70
+ ''
71
+ rescue => e
72
+ Rails.logger.error " ❌ Error rendering Razorpay Affordability block: #{e.message}"
73
+ "<div class='razorpay-affordability-error'>Error loading Razorpay Affordability Widget</div>".html_safe
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module PageSections
3
+ module ProductDetailsDecorator
4
+ def default_blocks
5
+ super + [Spree::PageBlocks::Products::RazorpayAffordability.new]
6
+ end
7
+
8
+ def available_blocks_to_add
9
+ super + [Spree::PageBlocks::Products::RazorpayAffordability]
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::PageSections::ProductDetails.prepend(
16
+ Spree::PageSections::ProductDetailsDecorator
17
+ )
18
+
@@ -0,0 +1,303 @@
1
+ <%# Admin form for Razorpay Affordability block %>
2
+ <br><h5 class="mb-3">Razorpay: Appearance & Color</h5><br>
3
+
4
+ <!-- Theme/Header color -->
5
+ <div class="mb-4 form-group"
6
+ data-controller="color-picker"
7
+ data-color-picker-clear-value="false"
8
+ data-color-picker-default-color-value="<%= f.object.preferred_theme_color.presence || '#800080' %>">
9
+ <div class="d-flex align-items-center">
10
+ <%= f.hidden_field :preferred_theme_color,
11
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
12
+ autocomplete: "off" %>
13
+
14
+ <div>
15
+ <label class="mb-0">Theme (Header) Color</label><br>
16
+ <span data-color-picker-target="value" class="text-muted">
17
+ <%= f.object.preferred_theme_color.presence || '#800080' %>
18
+ </span>
19
+ </div>
20
+
21
+ <div class="ml-auto" style="width:40px">
22
+ <div data-color-picker-target="picker"
23
+ class="border d-inline-block rounded-circle"
24
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_theme_color.presence || '#800080' %>;"
25
+ role="button" aria-label="toggle color picker dialog"></div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <hr>
31
+
32
+ <br><h5 class="mb-3">Heading Options</h5><br>
33
+ <div class="form-group">
34
+ <label>Heading Color</label>
35
+ <div class="mb-4 form-group"
36
+ data-controller="color-picker"
37
+ data-color-picker-clear-value="false"
38
+ data-color-picker-default-color-value="<%= f.object.preferred_heading_color.presence || '#000000' %>">
39
+ <%= f.hidden_field :preferred_heading_color,
40
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
41
+ autocomplete: "off" %>
42
+ <div class="d-flex align-items-center">
43
+ <span data-color-picker-target="value" class="text-muted">
44
+ <%= f.object.preferred_heading_color.presence || '#000000' %>
45
+ </span>
46
+ <div class="ml-auto" style="width:40px">
47
+ <div data-color-picker-target="picker"
48
+ class="border d-inline-block rounded-circle"
49
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_heading_color.presence || '#000000' %>;"
50
+ role="button"></div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="form-group">
57
+ <label>Heading Font Size</label>
58
+ <div data-controller="better-slider"
59
+ data-better-slider-label-for-min-value=""
60
+ data-better-slider-unit-value="px">
61
+ <div class="flex flex-row w-full">
62
+ <%= f.number_field :preferred_heading_font_size, type: 'range', min: 8, max: 48, step: 1,
63
+ class: 'custom-range',
64
+ data: { better_slider_target: "rangeInput", action: "change->auto-submit#submit" } %>
65
+ </div>
66
+ <span data-better-slider-target="currentValueLabel"><%= f.object.preferred_heading_font_size %>px</span>
67
+ </div>
68
+ </div>
69
+
70
+ <hr>
71
+
72
+ <br><h5 class="mb-3">Content Options</h5><br>
73
+ <div class="form-group">
74
+ <label>Content Background Color</label>
75
+ <div class="mb-4 form-group"
76
+ data-controller="color-picker"
77
+ data-color-picker-clear-value="false"
78
+ data-color-picker-default-color-value="<%= f.object.preferred_content_background_color.presence || '#ffffff' %>">
79
+ <%= f.hidden_field :preferred_content_background_color,
80
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
81
+ autocomplete: "off" %>
82
+ <div class="d-flex align-items-center">
83
+ <span data-color-picker-target="value" class="text-muted">
84
+ <%= f.object.preferred_content_background_color.presence || '#ffffff' %>
85
+ </span>
86
+ <div class="ml-auto" style="width:40px">
87
+ <div data-color-picker-target="picker"
88
+ class="border d-inline-block rounded-circle"
89
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_content_background_color.presence || '#ffffff' %>;"
90
+ role="button"></div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="form-group">
97
+ <label>Content Color</label>
98
+ <div class="mb-4 form-group"
99
+ data-controller="color-picker"
100
+ data-color-picker-clear-value="false"
101
+ data-color-picker-default-color-value="<%= f.object.preferred_content_color.presence || '#000000' %>">
102
+ <%= f.hidden_field :preferred_content_color,
103
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
104
+ autocomplete: "off" %>
105
+ <div class="d-flex align-items-center">
106
+ <span data-color-picker-target="value" class="text-muted">
107
+ <%= f.object.preferred_content_color.presence || '#000000' %>
108
+ </span>
109
+ <div class="ml-auto" style="width:40px">
110
+ <div data-color-picker-target="picker"
111
+ class="border d-inline-block rounded-circle"
112
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_content_color.presence || '#000000' %>;"
113
+ role="button"></div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="form-group">
120
+ <label>Content Font Size</label>
121
+ <div data-controller="better-slider"
122
+ data-better-slider-label-for-min-value=""
123
+ data-better-slider-unit-value="px">
124
+ <div class="flex flex-row w-full">
125
+ <%= f.number_field :preferred_content_font_size, type: 'range', min: 8, max: 30, step: 1,
126
+ class: 'custom-range',
127
+ data: { better_slider_target: "rangeInput", action: "change->auto-submit#submit" } %>
128
+ </div>
129
+ <span data-better-slider-target="currentValueLabel"><%= f.object.preferred_content_font_size %>px</span>
130
+ </div>
131
+ </div>
132
+
133
+ <hr>
134
+
135
+ <br><h5 class="mb-3">Discount & Link / Button</h5><br>
136
+
137
+ <div class="form-group">
138
+ <label>Discount Value Color</label>
139
+ <div class="mb-4 form-group"
140
+ data-controller="color-picker"
141
+ data-color-picker-clear-value="false"
142
+ data-color-picker-default-color-value="<%= f.object.preferred_discount_color.presence || '#e60099' %>">
143
+ <%= f.hidden_field :preferred_discount_color,
144
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
145
+ autocomplete: "off" %>
146
+ <div class="d-flex align-items-center">
147
+ <span data-color-picker-target="value" class="text-muted">
148
+ <%= f.object.preferred_discount_color.presence || '#e60099' %>
149
+ </span>
150
+ <div class="ml-auto" style="width:40px">
151
+ <div data-color-picker-target="picker"
152
+ class="border d-inline-block rounded-circle"
153
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_discount_color.presence || '#e60099' %>;"
154
+ role="button"></div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <div class="form-group">
161
+ <label>Show as Button?</label>
162
+ <%= f.select :preferred_link_button,
163
+ options_for_select([["Yes", true], ["No", false]], f.object.preferred_link_button),
164
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
165
+ </div>
166
+
167
+ <div class="form-group">
168
+ <label>Link Color</label>
169
+ <div class="mb-4 form-group"
170
+ data-controller="color-picker"
171
+ data-color-picker-clear-value="false"
172
+ data-color-picker-default-color-value="<%= f.object.preferred_link_color.presence || '#000000' %>">
173
+ <%= f.hidden_field :preferred_link_color,
174
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
175
+ autocomplete: "off" %>
176
+ <div class="d-flex align-items-center">
177
+ <span data-color-picker-target="value" class="text-muted">
178
+ <%= f.object.preferred_link_color.presence || '#000000' %>
179
+ </span>
180
+ <div class="ml-auto" style="width:40px">
181
+ <div data-color-picker-target="picker"
182
+ class="border d-inline-block rounded-circle"
183
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_link_color.presence || '#000000' %>;"
184
+ role="button"></div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <div class="form-group">
191
+ <label>Link Font Size</label>
192
+ <div data-controller="better-slider"
193
+ data-better-slider-label-for-min-value=""
194
+ data-better-slider-unit-value="px">
195
+ <div class="flex flex-row w-full">
196
+ <%= f.number_field :preferred_link_font_size, type: 'range', min: 8, max: 20, step: 1,
197
+ class: 'custom-range',
198
+ data: { better_slider_target: "rangeInput", action: "change->auto-submit#submit" } %>
199
+ </div>
200
+ <span data-better-slider-target="currentValueLabel"><%= f.object.preferred_link_font_size %>px</span>
201
+ </div>
202
+ </div>
203
+
204
+ <hr>
205
+
206
+ <br><h5 class="mb-3">Footer & Dark Mode</h5><br>
207
+
208
+ <div class="form-group">
209
+ <label>Footer Color</label>
210
+ <div class="mb-4 form-group"
211
+ data-controller="color-picker"
212
+ data-color-picker-clear-value="false"
213
+ data-color-picker-default-color-value="<%= f.object.preferred_footer_color.presence || '#000000' %>">
214
+ <%= f.hidden_field :preferred_footer_color,
215
+ data: { color_picker_target: "input", action: "change->auto-submit#submit" },
216
+ autocomplete: "off" %>
217
+ <div class="d-flex align-items-center">
218
+ <span data-color-picker-target="value" class="text-muted">
219
+ <%= f.object.preferred_footer_color.presence || '#000000' %>
220
+ </span>
221
+ <div class="ml-auto" style="width:40px">
222
+ <div data-color-picker-target="picker"
223
+ class="border d-inline-block rounded-circle"
224
+ style="height: 40px; width: 40px; background-color: <%= f.object.preferred_footer_color.presence || '#000000' %>;"
225
+ role="button"></div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="form-group">
232
+ <label>Footer Font Size</label>
233
+ <div data-controller="better-slider"
234
+ data-better-slider-label-for-min-value=""
235
+ data-better-slider-unit-value="px">
236
+ <div class="flex flex-row w-full">
237
+ <%= f.number_field :preferred_footer_font_size, type: 'range', min: 8, max: 20, step: 1,
238
+ class: 'custom-range',
239
+ data: { better_slider_target: "rangeInput", action: "change->auto-submit#submit" } %>
240
+ </div>
241
+ <span data-better-slider-target="currentValueLabel"><%= f.object.preferred_footer_font_size %>px</span>
242
+ </div>
243
+ </div>
244
+
245
+ <div class="form-group">
246
+ <label>Footer Dark Logo</label>
247
+ <%= f.select :preferred_footer_dark_logo,
248
+ options_for_select([["Light logo (default)", false], ["Dark logo", true]], f.object.preferred_footer_dark_logo),
249
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
250
+ </div>
251
+
252
+ <div class="form-group">
253
+ <label>Dark Mode</label>
254
+ <%= f.select :preferred_is_dark_mode,
255
+ options_for_select([["Off", false], ["On", true]], f.object.preferred_is_dark_mode),
256
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
257
+ </div>
258
+
259
+ <hr>
260
+
261
+ <br><h5 class="mb-3">Widget Settings (Enable / Disable)</h5><br>
262
+
263
+ <div class="form-group">
264
+ <label>Offers</label>
265
+ <%= f.select :preferred_offers_enabled,
266
+ options_for_select([["Enabled", true], ["Disabled", false]], f.object.preferred_offers_enabled),
267
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
268
+ </div>
269
+
270
+ <div class="form-group">
271
+ <label>EMI</label>
272
+ <%= f.select :preferred_emi_enabled,
273
+ options_for_select([["Enabled", true], ["Disabled", false]], f.object.preferred_emi_enabled),
274
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
275
+ </div>
276
+
277
+ <div class="form-group">
278
+ <label>Cardless EMI</label>
279
+ <%= f.select :preferred_cardless_emi_enabled,
280
+ options_for_select([["Enabled", true], ["Disabled", false]], f.object.preferred_cardless_emi_enabled),
281
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
282
+ </div>
283
+
284
+ <div class="form-group">
285
+ <label>Pay Later</label>
286
+ <%= f.select :preferred_paylater_enabled,
287
+ options_for_select([["Enabled", true], ["Disabled", false]], f.object.preferred_paylater_enabled),
288
+ {}, class: "custom-select", data: { action: "change->auto-submit#submit" } %>
289
+ </div>
290
+
291
+ <hr>
292
+
293
+ <br><h5 class="mb-3">Other / Keys</h5><br>
294
+
295
+ <div class="form-group">
296
+ <%= f.label :preferred_merchant_key_id, "Merchant Key ID (override gateway)" %>
297
+ <%= f.text_field :preferred_merchant_key_id, class: "form-control", data: { action: "change->auto-submit#submit" } %>
298
+ </div>
299
+
300
+ <div class="form-group">
301
+ <%= f.label :preferred_fallback_amount, "Fallback amount (paise)" %>
302
+ <%= f.number_field :preferred_fallback_amount, class: "form-control", data: { action: "change->auto-submit#submit" } %>
303
+ </div>
@@ -139,22 +139,23 @@
139
139
 
140
140
  <style>
141
141
  #razorpay-custom-button {
142
- background: linear-gradient(180deg, rgb(0 26 72) 0%, rgb(1 4 30) 50%, #000000 100%) !important;
143
- color: white !important;
144
- border: none !important;
145
- padding: 13px 100px 13px 100px !important;
146
- font-size: 0.8rem !important;
147
- font-weight: 400 !important;
148
- border-radius: 50px !important;
149
- cursor: pointer !important;
150
- width: max-content !important;
151
- place-self: center;
152
- margin: 20px 0 !important;
153
- box-shadow: 0 4px 15px rgba(19, 100, 241, 0.5) !important;
154
- transition: all 0.3s ease !important;
155
- display: flex !important;
156
- align-items: center !important;
157
- justify-content: center !important;
142
+ background: linear-gradient(180deg, rgb(0 26 72) 0%, rgb(1 4 30) 50%, #000000 100%) !important;
143
+ color: white !important;
144
+ border: none !important;
145
+ padding: 15px 70px 15px 70px !important;
146
+ font-size: 0.8rem !important;
147
+ font-weight: 400 !important;
148
+ border-radius: 50px !important;
149
+ cursor: pointer !important;
150
+ width: -webkit-fill-available;
151
+ max-width: max-content !important;
152
+ place-self: center;
153
+ margin: 20px 0 !important;
154
+ box-shadow: 0 4px 15px rgba(19, 100, 241, 0.5) !important;
155
+ transition: all 0.3s ease !important;
156
+ display: flex !important;
157
+ align-items: center !important;
158
+ justify-content: center !important;
158
159
  }
159
160
  #razorpay-custom-button:hover {
160
161
  box-shadow: 0 6px 20px rgba(19, 100, 241, 0.7) !important;
@@ -173,14 +174,21 @@
173
174
 
174
175
  <script src="https://checkout.razorpay.com/v1/checkout.js"></script>
175
176
  <div style="display: flex; justify-content: center;">
176
- <%= image_tag 'payment_icons/window_modal.svg', width: 140, height: 100, class: 'ml-2', alt: 'Razorpay Modal Window' %>
177
+ <%= image_tag 'payment_icons/window_modal.svg', width: 130, height: 100, class: 'ml-2', alt: 'Razorpay Modal Window' %>
177
178
  </div>
178
179
 
179
- <p style="max-width: 600px;margin: 10px auto;text-align: center;color: var(--color-neutral-600);font-size: 13px;">
180
- After clicking “Complete order”, you will be redirected to <br>
181
- Razorpay Secure (UPI, Cards, Wallets, NetBanking) to complete <br>
182
- your purchase securely.
180
+ <p style="
181
+ max-width: 100%;
182
+ margin: 10px auto;
183
+ text-align: center;
184
+ color: var(--color-neutral-600);
185
+ font-size: 0.9rem;
186
+ line-height: 1.5;
187
+ ">
188
+ Click ‘Pay via Razorpay’ to complete your <br>
189
+ purchase securely (UPI, Cards, Wallets, NetBanking). <br>
183
190
  </p>
191
+
184
192
  <div style="display: flex; justify-content: center; align-items: center;">
185
193
  <%= payment_method_icon_tag 'visa', class: 'm-1' %>
186
194
  <%= payment_method_icon_tag 'master', class: 'm-1' %>
@@ -0,0 +1,134 @@
1
+ <%
2
+ product = local_assigns[:product]
3
+ block = local_assigns[:block]
4
+ %>
5
+
6
+ <% if product.present? %>
7
+ <%
8
+ currency = current_currency rescue 'INR'
9
+ current_variant = local_assigns[:variant] || @selected_variant || product.default_variant
10
+ price_val = current_variant&.price_in(currency)&.amount || 0
11
+ amount_in_paise = (price_val * 100).to_i
12
+
13
+ # Key resolution (block override -> gateway)
14
+ key_id = block.preferred_merchant_key_id.presence
15
+ unless key_id.present?
16
+ gateway = Spree::Gateway::RazorpayGateway.active.available_on_front_end.first
17
+ key_id = gateway&.current_key_id
18
+ end
19
+ key_id = key_id.to_s.strip
20
+ %>
21
+
22
+ <% if key_id.present? && amount_in_paise > 0 %>
23
+
24
+ <div id="razorpay-affordability-widget" style="margin: 15px 0; min-height: 100px;"></div>
25
+
26
+ <script>
27
+ (function() {
28
+ // Build display.widget.main structure from block preferences
29
+ const display = {
30
+ widget: {
31
+ main: {
32
+ heading: {
33
+ color: "<%= j(block.preferred_heading_color.presence || '#000000') %>",
34
+ fontSize: "<%= (block.preferred_heading_font_size || 14).to_i %>px"
35
+ },
36
+ content: {
37
+ backgroundColor: "<%= j(block.preferred_content_background_color.presence || '#ffffff') %>",
38
+ color: "<%= j(block.preferred_content_color.presence || '#000000') %>",
39
+ fontSize: "<%= (block.preferred_content_font_size || 13).to_i %>px"
40
+ },
41
+ discount: {
42
+ color: "<%= j(block.preferred_discount_color.presence || '#e60099') %>"
43
+ },
44
+ link: {
45
+ button: <%= block.preferred_link_button ? 'true' : 'false' %>,
46
+ color: "<%= j(block.preferred_link_color.presence || '#000000') %>",
47
+ fontSize: "<%= (block.preferred_link_font_size || 12).to_i %>px"
48
+ },
49
+ footer: {
50
+ color: "<%= j(block.preferred_footer_color.presence || '#000000') %>",
51
+ fontSize: "<%= (block.preferred_footer_font_size || 12).to_i %>px",
52
+ darkLogo: <%= block.preferred_footer_dark_logo ? 'true' : 'false' %>
53
+ },
54
+ isDarkMode: <%= block.preferred_is_dark_mode ? 'true' : 'false' %>
55
+ }
56
+ }
57
+ };
58
+
59
+ const CONFIG = {
60
+ key: "<%= j(key_id) %>",
61
+ amount: <%= amount_in_paise %>,
62
+ currency: "<%= j(currency) %>",
63
+ theme: {
64
+ color: "<%= j(block.preferred_theme_color.presence || '#800080') %>"
65
+ },
66
+ display: {
67
+ ...display,
68
+ offers: <%= block.preferred_offers_enabled ? 'true' : 'false' %>,
69
+ emi: <%= block.preferred_emi_enabled ? 'true' : 'false' %>,
70
+ cardlessEmi: <%= block.preferred_cardless_emi_enabled ? 'true' : 'false' %>,
71
+ paylater: <%= block.preferred_paylater_enabled ? 'true' : 'false' %>
72
+ }
73
+ };
74
+
75
+ // Helper: Load Script if missing
76
+ function loadLib(callback) {
77
+ if (window.RazorpayAffordabilitySuite) { callback(); return; }
78
+
79
+ const src = "https://cdn.razorpay.com/widgets/affordability/affordability.js";
80
+ if (document.querySelector(`script[src="${src}"]`)) {
81
+ const check = setInterval(() => {
82
+ if (window.RazorpayAffordabilitySuite) { clearInterval(check); callback(); }
83
+ }, 100);
84
+ return;
85
+ }
86
+
87
+ const script = document.createElement('script');
88
+ script.src = src;
89
+ script.async = true;
90
+ script.onload = callback;
91
+ document.head.appendChild(script);
92
+ }
93
+
94
+ function initWidget() {
95
+ const container = document.getElementById('razorpay-affordability-widget');
96
+ if (!container) return;
97
+
98
+ if (container.classList.contains('rzp-container') || container.querySelector('iframe')) return;
99
+
100
+ try {
101
+ // Initialize Suite with CONFIG (object)
102
+ const suite = new RazorpayAffordabilitySuite(CONFIG);
103
+
104
+ // Render (no args defaults to element with id razorpay-affordability-widget)
105
+ suite.render();
106
+ } catch (err) {
107
+ console.error("Razorpay Widget Error:", err && err.message ? err.message : err);
108
+ }
109
+ }
110
+
111
+ // Initialize Logic
112
+ loadLib(() => {
113
+ initWidget();
114
+ setTimeout(initWidget, 500);
115
+ });
116
+
117
+ // Turbo integration
118
+ document.addEventListener("turbo:load", () => setTimeout(() => loadLib(initWidget), 200));
119
+ document.addEventListener("turbo:frame-load", () => setTimeout(() => loadLib(initWidget), 200));
120
+
121
+ })();
122
+ </script>
123
+
124
+ <% else %>
125
+ <% if current_spree_user&.admin? %>
126
+ <div class="text-red-500 border p-2 text-sm" style="margin: 10px 0;">
127
+ <strong>Razorpay Config Missing:</strong><br>
128
+ Ensure a Payment Method is active or a Key ID is set in block settings.<br>
129
+ Current Key: <%= key_id.presence || 'None' %>
130
+ </div>
131
+ <% end %>
132
+ <% end %>
133
+
134
+ <% end %>
@@ -0,0 +1,88 @@
1
+ <turbo-frame id="main-product-<%= product.id %>" target="_top">
2
+ <% current_variant = @selected_variant || @variant_from_options || product.first_or_default_variant(current_currency) %>
3
+ <div class="main-product-container" style="<%= section_styles(section) %>">
4
+ <div
5
+ class="page-container lg:mb-16"
6
+ <%= 'data-controller=product-form' %>
7
+ data-product-form-required-options-value='<%= product.option_type_ids.map(&:to_s).to_json %>'
8
+ data-product-form-selected-variant-disabled-value='<%= !@selected_variant&.in_stock? %>'
9
+ data-product-form-variant-from-options-disabled-value='<%= !@variant_from_options&.in_stock? %>'
10
+ data-product-form-frame-name-value="main-product-<%= product.id %>"
11
+ data-product-form-url-value="<%= spree.product_url(product) %>">
12
+ <template data-product-form-target="spinnerTemplate">
13
+ <%= render "spree/shared/icons/spinner" %>
14
+ </template>
15
+
16
+ <div id="product-details-page" class="grid grid-cols-1 lg:grid-cols-12 gap-x-14">
17
+ <% images = product_media_gallery_images(product, selected_variant: @selected_variant, variant_from_options: @variant_from_options) %>
18
+
19
+ <div class="lg:col-span-7 relative">
20
+ <div class="lg:hidden mb-6">
21
+ <%= render 'spree/products/media_gallery', images: images, product: product %>
22
+ </div>
23
+ <div class="hidden lg:block" data-product-form-target="desktopMediaGallery">
24
+ <%= render 'spree/products/media_gallery', images: images, desktop: true, product: product %>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="lg:col-span-5 lg:col-start-8">
29
+ <% show_waitlist_modal = spree.respond_to?(:waitlists_path) && current_variant.present? %>
30
+ <div
31
+ <% if show_waitlist_modal %>
32
+ data-controller="modal"
33
+ <% end %>
34
+ data-modal-allow-background-close="true"
35
+ class="h-full w-full waitlist-modal"
36
+ data-modal-backdrop-color-value="rgba(0,0,0,0.32)">
37
+ <%= form_with(url: spree.line_items_path, method: :post, data: { controller: "turbo-stream-form", product_form_target: "form" }) do |f| %>
38
+ <%= hidden_field_tag :variant_id, current_variant&.id %>
39
+
40
+ <div data-product-form-target="productDetails">
41
+ <% section.blocks.each do |block| %>
42
+ <div <%= block_attributes(block) %>>
43
+ <% case block.class.name %>
44
+ <% when 'Spree::PageBlocks::Products::Title' %>
45
+ <h1 class="text-2xl uppercase tracking-tight font-medium">
46
+ <%= product.name %>
47
+ </h1>
48
+ <% when 'Spree::PageBlocks::Products::Brand' %>
49
+ <% if product.brand_taxon %>
50
+ <%= link_to spree.nested_taxons_path(product.brand_taxon), title: product.brand_name do %>
51
+ <h3 class="text-sm lg:mt-0 inline-block mb-1">
52
+ <%= product.brand_name %>
53
+ </h3>
54
+ <% end %>
55
+ <% end %>
56
+ <% when 'Spree::PageBlocks::Products::Price' %>
57
+ <%= render 'spree/products/price', product: product, use_variant: true, selected_variant: @selected_variant, price_class: "lg:text-lg lg:font-medium", price_container_class: "w-full" %>
58
+ <% when 'Spree::PageBlocks::Products::VariantPicker' %>
59
+ <%= render 'spree/products/variant_picker', product: product, selected_variant: @selected_variant %>
60
+ <% when 'Spree::PageBlocks::Products::QuantitySelector' %>
61
+ <%= render 'spree/products/quantity_selector', product: product, selected_variant: @selected_variant %>
62
+ <% when 'Spree::PageBlocks::Products::BuyButtons' %>
63
+ <div class="flex w-full" data-controller='sticky-button'>
64
+ <%= render 'spree/products/add_to_cart_button', product: product, selected_variant: @selected_variant, sticky_button_classes: "w-full" %>
65
+ <%= render 'spree/products/add_to_wishlist', variant: current_variant, css_classes: 'btn-secondary ml-5 h-12 !py-0 !px-3 border-default', icon_size: 24 %>
66
+ </div>
67
+ <%# --- Razorpay Affordability Model --- %>
68
+ <% when 'Spree::PageBlocks::Products::RazorpayAffordability' %>
69
+ <%# We call your custom render method defined in the model %>
70
+ <%= block.render(self, product: product) %>
71
+ <%# ------------------------ %>
72
+ <% when 'Spree::PageBlocks::Products::Description' %>
73
+ <%= render 'spree/products/description', product: product, block: block, section: section %>
74
+ <% when 'Spree::PageBlocks::Metafields' %>
75
+ <%= render 'spree/products/metafields', product: product, block: block, section: section %>
76
+ <% end %>
77
+ </div>
78
+ <% end %>
79
+ <% end %>
80
+
81
+ <%= render 'spree/products/add_to_waitlist', variant: current_variant if show_waitlist_modal %>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <%= render 'spree/products/json_ld', product: product, selected_variant: @selected_variant %>
88
+ </turbo-frame>
@@ -0,0 +1 @@
1
+ config.eager_load_paths << Rails.root.join('app/models/spree/page_blocks')
@@ -1,4 +1,3 @@
1
-
2
1
  module SpreeRazorpayCheckout
3
2
  class Engine < Rails::Engine
4
3
  require 'spree/core'
@@ -7,7 +6,6 @@ module SpreeRazorpayCheckout
7
6
 
8
7
  require 'spree_razorpay_checkout/configuration'
9
8
 
10
- # use rspec for tests
11
9
  config.generators do |g|
12
10
  g.test_framework :rspec
13
11
  end
@@ -17,16 +15,31 @@ module SpreeRazorpayCheckout
17
15
  end
18
16
 
19
17
  def self.activate
18
+ # Load all decorators from the plugin's app directory
20
19
  Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
21
20
  Rails.configuration.cache_classes ? require(c) : load(c)
22
21
  end
23
22
  end
24
23
 
24
+ # Register Razorpay payment method
25
25
  config.after_initialize do |app|
26
26
  app.config.spree.payment_methods ||= []
27
27
  app.config.spree.payment_methods << ::Spree::Gateway::RazorpayGateway
28
28
  end
29
29
 
30
- config.to_prepare(&method(:activate).to_proc)
30
+ # Register custom PDP Page Block
31
+ config.to_prepare do
32
+ # 1. Force load the class so 'defined?' becomes true
33
+ # Ensure the file exists at: app/models/spree/page_blocks/products/razorpay_affordability.rb
34
+ require_dependency 'spree/page_blocks/products/razorpay_affordability' rescue nil
35
+
36
+ # 2. Register the block unconditionally if Spree::PageBlock exists
37
+ if defined?(Spree::PageBlock)
38
+ Spree::PageBlock.register_block(Spree::PageBlocks::Products::RazorpayAffordability)
39
+ end
40
+
41
+ # 3. Activate decorators
42
+ SpreeRazorpayCheckout::Engine.activate
43
+ end
31
44
  end
32
- end
45
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeRazorpayCheckout
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.3'.freeze
3
3
 
4
4
  module_function
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_razorpay_checkout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Umesh Ravani
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-03 00:00:00.000000000 Z
11
+ date: 2025-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: razorpay
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '5.0'
33
+ version: '5.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '5.0'
40
+ version: '5.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: spree_backend
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -102,6 +102,7 @@ extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
104
  - README.md
105
+ - app/assets/images/payment_icons/icon_razorpay.svg
105
106
  - app/assets/images/payment_icons/razorpay.svg
106
107
  - app/assets/images/payment_icons/razorpay_logo_dark.svg
107
108
  - app/assets/images/payment_icons/razorpay_logo_light.svg
@@ -110,8 +111,11 @@ files:
110
111
  - app/assets/javascripts/spree/frontend/process_razorpay.js
111
112
  - app/assets/javascripts/spree/frontend/spree_razorpay.js
112
113
  - app/controllers/concerns/spree/razor_pay.rb
114
+ - app/controllers/spree/products_controller_decorator.rb
113
115
  - app/controllers/spree/razorpay_controller.rb
114
116
  - app/models/spree/gateway/razorpay_gateway.rb
117
+ - app/models/spree/page_blocks/products/razorpay_affordability.rb
118
+ - app/models/spree/page_sections/product_details_decorator.rb
115
119
  - app/models/spree/razorpay_checkout.rb
116
120
  - app/models/spree_razorpay_checkout/configuration.rb
117
121
  - app/models/spree_razorpay_checkout/spree/order_decorator.rb
@@ -119,9 +123,13 @@ files:
119
123
  - app/services/razorpay/base.rb
120
124
  - app/services/razorpay/rp_order/api.rb
121
125
  - app/services/razorpay_refund.rb
126
+ - app/views/spree/admin/page_blocks/forms/_razorpay_affordability.html.erb
122
127
  - app/views/spree/admin/payment_methods/configuration_guides/_razorpay.html.erb
123
128
  - app/views/spree/admin/payment_methods/descriptions/_razorpay.html.erb
124
129
  - app/views/spree/checkout/payment/_razorpay.html.erb
130
+ - app/views/spree/page_blocks/products/razorpay_affordability/_razorpay_affordability.html.erb
131
+ - app/views/themes/default/spree/page_sections/_product_details.html.erb
132
+ - config/application.rb
125
133
  - config/initializers/assets.rb
126
134
  - config/locales/en.yml
127
135
  - config/routes.rb
@@ -156,5 +164,5 @@ requirements: []
156
164
  rubygems_version: 3.5.3
157
165
  signing_key:
158
166
  specification_version: 4
159
- summary: Razorpay integration for Spree Commerce 5.x
167
+ summary: Razorpay integration for Spree Commerce 5.2
160
168
  test_files: []