Class: Gateway::RazorpayGateway

Inherits:
Gateway
  • Object
show all
Defined in:
app/models/spree/gateway/razorpay_gateway.rb

Instance Method Summary collapse

Instance Method Details

#actionsObject



62
63
64
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 62

def actions
  %w[capture void credit]
end

#auto_capture?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 54

def auto_capture?
  true
end

#can_capture?(payment) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 66

def can_capture?(payment)
  %w[checkout pending].include?(payment.state)
end

#can_void?(payment) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 70

def can_void?(payment)
  payment.state != 'void'
end

#cancel(response_code, _source = nil, _options = {}) ⇒ Object



300
301
302
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 300

def cancel(response_code, _source = nil, _options = {})
  void(response_code)
end

#capture(*args) ⇒ Object



260
261
262
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 260

def capture(*args)
  ActiveMerchant::Billing::Response.new(true, 'Already Captured', {}, test: preferred_test_mode)
end

#complete_payment_session(payment_session:, params: {}) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 151

def complete_payment_session(payment_session:, params: {})
  provider
  
  ext_data = params[:external_data] || params['external_data'] || {}
  rzp_payment_id = ext_data[:razorpay_payment_id] || ext_data['razorpay_payment_id'] || payment_session.external_data['razorpay_payment_id']
  rzp_signature  = ext_data[:razorpay_signature] || ext_data['razorpay_signature'] || payment_session.external_data['razorpay_signature']

  begin
    ::Razorpay::Utility.verify_payment_signature(
      razorpay_order_id: payment_session.external_id,
      razorpay_payment_id: rzp_payment_id,
      razorpay_signature: rzp_signature
    )

    rzp_payment = ::Razorpay::Payment.fetch(rzp_payment_id)
    if rzp_payment.status == 'authorized'
      rzp_payment.capture({ amount: (payment_session.amount.to_f * 100).to_i }) 
    end

    payment_session.process! if payment_session.can_process?
    payment = payment_session.find_or_create_payment!
    
    if payment.present? && !payment.completed?
      payment.started_processing! if payment.checkout?
      payment.complete! if payment.can_complete?
    end

    payment_session.complete! if payment_session.can_complete?

  rescue StandardError => e
    Rails.logger.error("Razorpay 5.4 API Completion Failed: #{e.message}")
    payment_session.fail! if payment_session.can_fail?
    raise Spree::Core::GatewayError, e.message
  end
end

#configuration_guide_partial_nameObject



34
35
36
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 34

def configuration_guide_partial_name
  'razorpay'
end

#create_payment_session(order:, amount: nil, external_data: {}) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 101

def create_payment_session(order:, amount: nil, external_data: {})
  provider
  total = amount || order.total_minus_store_credits
  amount_in_cents = (total.to_f * 100).to_i

  rzp_order = ::Razorpay::Order.create(
    amount: amount_in_cents,
    currency: order.currency || 'INR',
    receipt: order.number,
    payment_capture: 1,
    notes: { spree_order_number: order.number, email: order.email }
  )

  unless rzp_order && rzp_order.attributes.key?('id')
    raise Spree::Core::GatewayError, 'Failed to create Razorpay session'
  end

  payment_sessions.create!(
    type: 'Spree::PaymentSessions::Razorpay',
    order: order,
    amount: total,
    currency: order.currency || 'INR',
    external_id: rzp_order.id,
    external_data: { client_key: current_key_id },
    customer: order.user,
    status: 'pending'
  )
rescue StandardError => e
  Rails.logger.error("Razorpay Session Creation Failed: #{e.message}")
  raise Spree::Core::GatewayError, e.message
end

#credit(credit_cents, response_code, _gateway_options = {}) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 264

def credit(credit_cents, response_code, _gateway_options = {})
  provider
  begin
    rzp_payment_id = resolve_razorpay_payment_id(response_code)
    
    if rzp_payment_id.blank?
      raise StandardError, "Missing Razorpay Payment ID. Cannot process refund."
    end
    
    refund = ::Razorpay::Refund.create(payment_id: rzp_payment_id, amount: credit_cents.to_i)
    
    ActiveMerchant::Billing::Response.new(true, 'Razorpay Refund Successful', { refund_id: refund.id }, test: preferred_test_mode, authorization: refund.id)
  rescue StandardError => e
    Rails.logger.error("Razorpay Refund Failed: #{e.message}")
    ActiveMerchant::Billing::Response.new(false, "Refund failed: #{e.message}", {}, test: preferred_test_mode)
  end
end

#current_key_idObject



46
47
48
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 46

def current_key_id
  preferred_test_mode ? preferred_test_key_id : preferred_key_id
end

#current_key_secretObject



50
51
52
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 50

def current_key_secret
  preferred_test_mode ? preferred_test_key_secret : preferred_key_secret
end

#description_partial_nameObject



30
31
32
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 30

def description_partial_name
  'razorpay'
end

#method_typeObject



22
23
24
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 22

def method_type
  'razorpay'
end

#nameObject



18
19
20
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 18

def name
  'Razorpay Secure (UPI, Wallets, Cards & Netbanking)'
end

#parse_webhook_event(raw_body, headers) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 187

def parse_webhook_event(raw_body, headers)
  provider
  signature = headers['HTTP_X_RAZORPAY_SIGNATURE'] || headers['X-Razorpay-Signature']

  unless ::Razorpay::Utility.verify_webhook_signature(raw_body, signature, preferred_webhook_secret)
    raise Spree::PaymentMethod::WebhookSignatureError
  end

  event = JSON.parse(raw_body)
  payment_entity = event.dig('payload', 'payment', 'entity') || event.dig('payload', 'order', 'entity')
  
  session = Spree::PaymentSession.find_by(external_id: payment_entity['order_id'])
  return nil unless session

  case event['event']
  when 'payment.captured', 'payment.authorized'
    { action: :captured, payment_session: session }
  when 'payment.failed'
    { action: :failed, payment_session: session }
  else
    nil
  end
rescue ::Razorpay::Errors::SignatureVerificationError
  raise Spree::PaymentMethod::WebhookSignatureError
end

#payment_icon_nameObject



26
27
28
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 26

def payment_icon_name
  'razorpay'
end

#payment_session_classObject



94
95
96
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 94

def payment_session_class
  Spree::PaymentSessions::Razorpay if defined?(Spree::PaymentSession)
end

#payment_source_classObject



90
91
92
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 90

def payment_source_class
  Spree::RazorpayCheckout
end

#providerObject



42
43
44
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 42

def provider
  ::Razorpay.setup(current_key_id, current_key_secret)
end

#provider_classObject



38
39
40
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 38

def provider_class
  self
end

#purchase(_amount, source, _gateway_options = {}) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 214

def purchase(_amount, source, _gateway_options = {})
  provider

  begin
    if source.razorpay_payment_id.blank? || source.razorpay_signature.blank?
       return ActiveMerchant::Billing::Response.new(false, 'Payment was not completed. Please try again.', {}, test: preferred_test_mode)
    end

    ::Razorpay::Utility.verify_payment_signature(
      razorpay_order_id: source.razorpay_order_id,
      razorpay_payment_id: source.razorpay_payment_id,
      razorpay_signature: source.razorpay_signature
    )

    rzp_payment = ::Razorpay::Payment.fetch(source.razorpay_payment_id)
    if rzp_payment.status == 'authorized'
      rzp_payment.capture({ amount: _amount })
    end

    source.update!(status: 'captured')
    
    ActiveMerchant::Billing::Response.new(true, 'Razorpay Payment Successful', {}, test: preferred_test_mode, authorization: source.razorpay_payment_id)
    
  rescue StandardError => e
    Rails.logger.error("Razorpay Verification/Capture Failed: #{e.message}")
    ActiveMerchant::Billing::Response.new(false, 'Payment verification failed.', {}, test: preferred_test_mode)
  end
end

#request_typeObject



58
59
60
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 58

def request_type
  'DEFAULT'
end

#resolve_razorpay_payment_id(response_code) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 243

def resolve_razorpay_payment_id(response_code)
  return nil if response_code.blank?

  if response_code.to_s.start_with?('order_')
    rzp_order = ::Razorpay::Order.fetch(response_code)
    payments = rzp_order.payments
    
    captured_payment = payments.items.find { |p| p.status == 'captured' } || payments.items.first
    
    raise StandardError, "No captured payment found for Razorpay Order #{response_code}" unless captured_payment
    
    captured_payment.id
  else
    response_code
  end
end

#session_required?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 78

def session_required?
  preferred_headless_api_mode
end

#setup_session_supported?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 86

def setup_session_supported?
  false
end

#source_required?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 82

def source_required?
  !preferred_headless_api_mode
end

#supports?(_source) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 74

def supports?(_source)
  true
end

#update_payment_session(payment_session:, amount: nil, external_data: {}) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 133

def update_payment_session(payment_session:, amount: nil, external_data: {})
  provider
  
  if amount.present? && payment_session.amount != amount
    amount_in_cents = (amount.to_f * 100).to_i
    
    new_rzp_order = ::Razorpay::Order.create(
      amount: amount_in_cents,
      currency: payment_session.currency,
      receipt: payment_session.order.number,
      payment_capture: 1
    )
    
    payment_session.update!(amount: amount, external_id: new_rzp_order.id)
  end
  payment_session
end

#void(response_code, _gateway_options = {}) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/models/spree/gateway/razorpay_gateway.rb', line 282

def void(response_code, _gateway_options = {})
  provider
  begin
    rzp_payment_id = resolve_razorpay_payment_id(response_code)

    if rzp_payment_id.blank?
      raise StandardError, "Missing Razorpay Payment ID. Cannot process void."
    end

    refund = ::Razorpay::Refund.create(payment_id: rzp_payment_id)
    
    ActiveMerchant::Billing::Response.new(true, 'Razorpay Void/Refund Successful', { refund_id: refund.id }, test: preferred_test_mode, authorization: refund.id)
  rescue StandardError => e
    Rails.logger.error("Razorpay Void Failed: #{e.message}")
    ActiveMerchant::Billing::Response.new(false, "Void failed: #{e.message}", {}, test: preferred_test_mode)
  end
end