spree_razorpay_checkout 0.1.3 → 0.2.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.
- checksums.yaml +4 -4
- data/app/controllers/spree/products_controller_decorator.rb +1 -1
- data/app/controllers/spree/razorpay/webhooks_controller.rb +35 -0
- data/app/javascript/spree_razorpay/application.js +12 -0
- data/app/javascript/spree_razorpay/controllers/checkout_razorpay_controller.js +108 -0
- data/app/jobs/spree_razorpay_checkout/handle_webhook_event_job.rb +64 -0
- data/app/models/spree/gateway/razorpay_gateway.rb +96 -53
- data/app/models/spree/page_blocks/products/razorpay_affordability.rb +4 -4
- data/app/models/spree/page_sections/product_details_decorator.rb +0 -1
- data/app/models/spree/payment_sessions/razorpay.rb +43 -0
- data/app/models/spree/razorpay_checkout.rb +1 -0
- data/app/services/razorpay/rp_order/api.rb +9 -1
- data/app/views/spree/admin/payment_methods/configuration_guides/_razorpay.html.erb +1 -1
- data/app/views/spree/admin/payment_methods/descriptions/_razorpay.html.erb +1 -0
- data/app/views/spree/checkout/payment/_razorpay.html.erb +121 -186
- data/config/routes.rb +4 -4
- data/lib/generators/spree_razorpay_checkout/install/install_generator.rb +21 -3
- data/lib/spree_razorpay_checkout/engine.rb +11 -7
- data/lib/spree_razorpay_checkout/version.rb +1 -1
- metadata +13 -43
- data/README.md +0 -188
- data/app/assets/images/payment_icons/icon_razorpay.svg +0 -20
- data/app/assets/javascripts/spree/frontend/process_razorpay.js +0 -40
- data/app/assets/javascripts/spree/frontend/spree_razorpay.js +0 -33
- data/app/views/themes/default/spree/page_sections/_product_details.html.erb +0 -88
- data/config/application.rb +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 210a8729bc5810c991d970ad880aa0a0ac8570e9eccfd0de194e13cb600d0136
|
|
4
|
+
data.tar.gz: 2198af0c4bdf320756d3ca5a197a53b7f34892332f649e284ad98892355aeadd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5fc643f93e86598dbf6586e333a54af34ceb300b523c3a6168bedb2164ed1112adb53707c9ac22637b54f1a1c77c1f44edbd1a96a81cda28de9147fbe7aee4eb
|
|
7
|
+
data.tar.gz: 1df23f297c1a5629721d24d6db194a2b1ae2a8b6adf3b4eacd46ec658e8d4791637ec0d4282aff14876c9d1bf120621b238ad99f5cc618598f508e679cb4a497
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Razorpay
|
|
3
|
+
class WebhooksController < ActionController::API
|
|
4
|
+
skip_before_action :verify_authenticity_token, raise: false
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
payload = request.body.read
|
|
8
|
+
signature = request.headers['X-Razorpay-Signature']
|
|
9
|
+
|
|
10
|
+
gateway = Spree::Gateway::RazorpayGateway.active.first
|
|
11
|
+
return head :not_found unless gateway && gateway.preferred_webhook_secret.present?
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
::Razorpay::Utility.verify_webhook_signature(
|
|
15
|
+
payload,
|
|
16
|
+
signature,
|
|
17
|
+
gateway.preferred_webhook_secret
|
|
18
|
+
)
|
|
19
|
+
rescue ::Razorpay::Errors::SignatureVerificationError => e
|
|
20
|
+
Rails.logger.error("Razorpay Webhook Verification Failed: #{e.message}")
|
|
21
|
+
return head :unauthorized
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
event = JSON.parse(payload)
|
|
25
|
+
|
|
26
|
+
# Listen for 'payment.authorized' which fires immediately after OTP!
|
|
27
|
+
if ['order.paid', 'payment.captured', 'payment.authorized'].include?(event['event'])
|
|
28
|
+
::SpreeRazorpayCheckout::HandleWebhookEventJob.perform_later(event)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
head :ok
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Application } from '@hotwired/stimulus'
|
|
2
|
+
import CheckoutRazorpayController from './controllers/checkout_razorpay_controller'
|
|
3
|
+
|
|
4
|
+
let application;
|
|
5
|
+
if (typeof window.Stimulus === "undefined") {
|
|
6
|
+
application = Application.start()
|
|
7
|
+
window.Stimulus = application
|
|
8
|
+
} else {
|
|
9
|
+
application = window.Stimulus
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
application.register('checkout-razorpay', CheckoutRazorpayController)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
paymentMethodId: String,
|
|
6
|
+
keyId: String,
|
|
7
|
+
orderId: String,
|
|
8
|
+
amount: Number,
|
|
9
|
+
currency: String,
|
|
10
|
+
merchantName: String,
|
|
11
|
+
merchantDesc: String,
|
|
12
|
+
themeColor: String,
|
|
13
|
+
userName: String,
|
|
14
|
+
userEmail: String,
|
|
15
|
+
userContact: String
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static targets = [
|
|
19
|
+
'paymentId',
|
|
20
|
+
'orderId',
|
|
21
|
+
'signature'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
connect() {
|
|
25
|
+
this.form = document.querySelector("#checkout_form_payment")
|
|
26
|
+
this.submitBtn = document.getElementById("checkout-payment-submit")
|
|
27
|
+
|
|
28
|
+
// Bind to the form submit event, NOT the button click!
|
|
29
|
+
// This perfectly intercepts Spree 5.3's native validation flows.
|
|
30
|
+
this.submitHandler = this.submit.bind(this)
|
|
31
|
+
this.form.addEventListener('submit', this.submitHandler)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
disconnect() {
|
|
35
|
+
if (this.form) {
|
|
36
|
+
this.form.removeEventListener('submit', this.submitHandler)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
submit(e) {
|
|
41
|
+
// Check if Razorpay is the currently selected radio button
|
|
42
|
+
const selectedRadio = document.querySelector('input[name="order[payments_attributes][][payment_method_id]"]:checked');
|
|
43
|
+
const isRazorpay = selectedRadio && selectedRadio.value === this.paymentMethodIdValue;
|
|
44
|
+
|
|
45
|
+
if (!isRazorpay) return; // If it's a different gateway, let Spree handle it normally
|
|
46
|
+
|
|
47
|
+
// If we already have the Razorpay signature populated, let the form submit natively to the backend!
|
|
48
|
+
if (this.paymentIdTarget.value && this.signatureTarget.value) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Otherwise, STOP the form submission and open Razorpay
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopImmediatePropagation();
|
|
55
|
+
|
|
56
|
+
this.setLoading(true);
|
|
57
|
+
|
|
58
|
+
const options = {
|
|
59
|
+
key: this.keyIdValue,
|
|
60
|
+
order_id: this.orderIdValue,
|
|
61
|
+
amount: this.amountValue,
|
|
62
|
+
currency: this.currencyValue,
|
|
63
|
+
name: this.merchantNameValue,
|
|
64
|
+
description: this.merchantDescValue,
|
|
65
|
+
handler: this.handleSuccess.bind(this),
|
|
66
|
+
modal: {
|
|
67
|
+
ondismiss: this.handleDismiss.bind(this)
|
|
68
|
+
},
|
|
69
|
+
prefill: {
|
|
70
|
+
name: this.userNameValue,
|
|
71
|
+
email: this.userEmailValue,
|
|
72
|
+
contact: this.userContactValue
|
|
73
|
+
},
|
|
74
|
+
theme: {
|
|
75
|
+
color: this.themeColorValue
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const rzp = new window.Razorpay(options);
|
|
80
|
+
rzp.open();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
handleSuccess(response) {
|
|
84
|
+
// 1. Populate the hidden fields with the secure response
|
|
85
|
+
this.paymentIdTarget.value = response.razorpay_payment_id;
|
|
86
|
+
this.orderIdTarget.value = response.razorpay_order_id;
|
|
87
|
+
this.signatureTarget.value = response.razorpay_signature;
|
|
88
|
+
|
|
89
|
+
// 2. Submit the form natively to Spree's backend!
|
|
90
|
+
// requestSubmit() ensures Turbo and Spree's event listeners fire correctly
|
|
91
|
+
this.form.requestSubmit();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
handleDismiss() {
|
|
95
|
+
this.setLoading(false);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setLoading(isLoading) {
|
|
99
|
+
if (isLoading) {
|
|
100
|
+
this.submitBtn.disabled = true;
|
|
101
|
+
this.submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Processing...';
|
|
102
|
+
} else {
|
|
103
|
+
this.submitBtn.disabled = false;
|
|
104
|
+
// Reset Spree button text depending on what it usually says
|
|
105
|
+
this.submitBtn.innerHTML = 'Save and Continue';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module SpreeRazorpayCheckout
|
|
2
|
+
class HandleWebhookEventJob < ActiveJob::Base
|
|
3
|
+
queue_as :default
|
|
4
|
+
|
|
5
|
+
def perform(event)
|
|
6
|
+
payload = event['payload']
|
|
7
|
+
payment_entity = payload.dig('payment', 'entity') || payload.dig('order', 'entity')
|
|
8
|
+
|
|
9
|
+
# Razorpay might send the ID in slightly different places depending on the event
|
|
10
|
+
razorpay_order_id = payment_entity['order_id'] || payload.dig('order', 'entity', 'id')
|
|
11
|
+
razorpay_payment_id = payment_entity['id']
|
|
12
|
+
|
|
13
|
+
checkout_record = Spree::RazorpayCheckout.find_by(razorpay_order_id: razorpay_order_id)
|
|
14
|
+
return unless checkout_record
|
|
15
|
+
|
|
16
|
+
order = checkout_record.order
|
|
17
|
+
return if order.completed? || order.canceled?
|
|
18
|
+
|
|
19
|
+
gateway = Spree::Gateway::RazorpayGateway.active.first
|
|
20
|
+
return unless gateway
|
|
21
|
+
|
|
22
|
+
order.with_lock do
|
|
23
|
+
# 1. Capture the abandoned payment via API
|
|
24
|
+
begin
|
|
25
|
+
rzp_payment = ::Razorpay::Payment.fetch(razorpay_payment_id)
|
|
26
|
+
if rzp_payment.status == 'authorized'
|
|
27
|
+
amount_in_cents = (order.total_minus_store_credits.to_f * 100).to_i
|
|
28
|
+
rzp_payment.capture({ amount: amount_in_cents })
|
|
29
|
+
end
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
Rails.logger.error("Webhook Razorpay Capture Failed: #{e.message}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
checkout_record.update!(
|
|
35
|
+
razorpay_payment_id: razorpay_payment_id,
|
|
36
|
+
status: 'captured'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# 2. Create the Spree::Payment record natively
|
|
40
|
+
payment = order.payments.find_or_create_by!(
|
|
41
|
+
response_code: razorpay_payment_id,
|
|
42
|
+
payment_method_id: gateway.id
|
|
43
|
+
) do |p|
|
|
44
|
+
p.amount = order.total
|
|
45
|
+
p.source = checkout_record
|
|
46
|
+
p.state = 'checkout'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 3. Mark the payment as completed
|
|
50
|
+
payment.process! if payment.checkout?
|
|
51
|
+
payment.complete! if payment.pending? || payment.processing?
|
|
52
|
+
|
|
53
|
+
# Tell Spree to recalculate the payment state
|
|
54
|
+
order.updater.update_payment_state
|
|
55
|
+
|
|
56
|
+
# Loop through the checkout steps (Payment -> Confirm -> Complete)
|
|
57
|
+
until order.completed? || order.state == 'complete'
|
|
58
|
+
order.next!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -2,10 +2,11 @@ require 'razorpay'
|
|
|
2
2
|
|
|
3
3
|
module Spree
|
|
4
4
|
class Gateway::RazorpayGateway < Gateway
|
|
5
|
+
preference :webhook_secret, :password
|
|
5
6
|
preference :key_id, :string
|
|
6
|
-
preference :key_secret, :
|
|
7
|
+
preference :key_secret, :password
|
|
7
8
|
preference :test_key_id, :string
|
|
8
|
-
preference :test_key_secret, :
|
|
9
|
+
preference :test_key_secret, :password
|
|
9
10
|
preference :test_mode, :boolean, default: false
|
|
10
11
|
preference :merchant_name, :string, default: 'Razorpay'
|
|
11
12
|
preference :merchant_description, :text, default: 'Razorpay Payment Gateway'
|
|
@@ -17,7 +18,11 @@ module Spree
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def source_required?
|
|
20
|
-
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def payment_source_class
|
|
25
|
+
Spree::RazorpayCheckout
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def name
|
|
@@ -28,10 +33,6 @@ module Spree
|
|
|
28
33
|
'razorpay'
|
|
29
34
|
end
|
|
30
35
|
|
|
31
|
-
def payment_source_class
|
|
32
|
-
'razorpay'
|
|
33
|
-
end
|
|
34
|
-
|
|
35
36
|
def payment_icon_name
|
|
36
37
|
'razorpay'
|
|
37
38
|
end
|
|
@@ -49,7 +50,7 @@ module Spree
|
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def provider
|
|
52
|
-
Razorpay.setup(current_key_id, current_key_secret)
|
|
53
|
+
::Razorpay.setup(current_key_id, current_key_secret)
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def current_key_id
|
|
@@ -69,7 +70,7 @@ module Spree
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def actions
|
|
72
|
-
%w[capture void]
|
|
73
|
+
%w[capture void credit]
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def can_capture?(payment)
|
|
@@ -80,63 +81,105 @@ module Spree
|
|
|
80
81
|
payment.state != 'void'
|
|
81
82
|
end
|
|
82
83
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
# -------------------------------------------------------------------------
|
|
85
|
+
# PURCHASE (Happy Path & Webhook Recovery)
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
def purchase(_amount, source, _gateway_options = {})
|
|
88
|
+
provider
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
if source.razorpay_payment_id.blank? || source.razorpay_signature.blank?
|
|
92
|
+
return ActiveMerchant::Billing::Response.new(false, 'Payment was not completed. Please try again.', {}, test: preferred_test_mode)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# 1. Verify the signature
|
|
96
|
+
::Razorpay::Utility.verify_payment_signature(
|
|
97
|
+
razorpay_order_id: source.razorpay_order_id,
|
|
98
|
+
razorpay_payment_id: source.razorpay_payment_id,
|
|
99
|
+
razorpay_signature: source.razorpay_signature
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# 2. Safely ensure it is captured!
|
|
103
|
+
rzp_payment = ::Razorpay::Payment.fetch(source.razorpay_payment_id)
|
|
104
|
+
if rzp_payment.status == 'authorized'
|
|
105
|
+
rzp_payment.capture({ amount: _amount })
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
source.update!(status: 'captured')
|
|
109
|
+
|
|
110
|
+
ActiveMerchant::Billing::Response.new(
|
|
111
|
+
true,
|
|
112
|
+
'Razorpay Payment Successful',
|
|
113
|
+
{},
|
|
114
|
+
test: preferred_test_mode,
|
|
115
|
+
authorization: source.razorpay_payment_id
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
Rails.logger.error("Razorpay Verification/Capture Failed: #{e.message}")
|
|
120
|
+
ActiveMerchant::Billing::Response.new(false, 'Payment verification failed.', {}, test: preferred_test_mode)
|
|
121
|
+
end
|
|
86
122
|
end
|
|
87
123
|
|
|
88
124
|
def capture(*args)
|
|
89
|
-
|
|
125
|
+
# We already auto-capture via the frontend/webhook, so we return true to keep Spree happy
|
|
126
|
+
ActiveMerchant::Billing::Response.new(true, 'Already Captured', {}, test: preferred_test_mode)
|
|
90
127
|
end
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
129
|
+
# -------------------------------------------------------------------------
|
|
130
|
+
# REFUNDS & CANCELLATIONS
|
|
131
|
+
# -------------------------------------------------------------------------
|
|
95
132
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
133
|
+
# Triggered when you click "Refund" in the Spree Admin
|
|
134
|
+
def credit(credit_cents, response_code, _gateway_options = {})
|
|
135
|
+
provider
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
begin
|
|
138
|
+
# Fetch the original payment from Razorpay using the saved payment ID
|
|
139
|
+
rzp_payment = ::Razorpay::Payment.fetch(response_code)
|
|
140
|
+
|
|
141
|
+
# Issue the refund via Razorpay API (amount must be in paise/cents)
|
|
142
|
+
refund = rzp_payment.refund(amount: credit_cents)
|
|
143
|
+
|
|
144
|
+
ActiveMerchant::Billing::Response.new(
|
|
145
|
+
true,
|
|
146
|
+
'Razorpay Refund Successful',
|
|
147
|
+
{ refund_id: refund.id },
|
|
148
|
+
test: preferred_test_mode,
|
|
149
|
+
authorization: refund.id
|
|
150
|
+
)
|
|
151
|
+
rescue StandardError => e
|
|
152
|
+
Rails.logger.error("Razorpay Refund Failed: #{e.message}")
|
|
153
|
+
ActiveMerchant::Billing::Response.new(false, "Refund failed: #{e.message}", {}, test: preferred_test_mode)
|
|
154
|
+
end
|
|
116
155
|
end
|
|
117
156
|
|
|
118
|
-
#
|
|
119
|
-
def
|
|
120
|
-
|
|
157
|
+
# Triggered if you explicitly "Void" a payment in Spree
|
|
158
|
+
def void(response_code, _gateway_options = {})
|
|
159
|
+
provider
|
|
121
160
|
|
|
122
161
|
begin
|
|
123
|
-
payment
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
162
|
+
# Razorpay doesn't have a concept of "Voiding" a captured payment,
|
|
163
|
+
# so we just issue a full refund instead.
|
|
164
|
+
rzp_payment = ::Razorpay::Payment.fetch(response_code)
|
|
165
|
+
refund = rzp_payment.refund
|
|
166
|
+
|
|
167
|
+
ActiveMerchant::Billing::Response.new(
|
|
168
|
+
true,
|
|
169
|
+
'Razorpay Void/Refund Successful',
|
|
170
|
+
{ refund_id: refund.id },
|
|
171
|
+
test: preferred_test_mode,
|
|
172
|
+
authorization: refund.id
|
|
173
|
+
)
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
Rails.logger.error("Razorpay Void Failed: #{e.message}")
|
|
176
|
+
ActiveMerchant::Billing::Response.new(false, "Void failed: #{e.message}", {}, test: preferred_test_mode)
|
|
133
177
|
end
|
|
134
178
|
end
|
|
135
179
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
ActiveMerchant::Billing::Response.new(true, '', {}, {})
|
|
180
|
+
# Triggered if the entire Order is Cancelled in the Spree Admin
|
|
181
|
+
def cancel(response_code)
|
|
182
|
+
void(response_code)
|
|
140
183
|
end
|
|
141
184
|
end
|
|
142
|
-
end
|
|
185
|
+
end
|
|
@@ -55,7 +55,7 @@ module Spree
|
|
|
55
55
|
is_available = available?(locals)
|
|
56
56
|
Rails.logger.info " Available check: #{is_available}"
|
|
57
57
|
unless is_available
|
|
58
|
-
Rails.logger.warn "
|
|
58
|
+
Rails.logger.warn " Block marked as not available, but rendering anyway"
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -63,13 +63,13 @@ module Spree
|
|
|
63
63
|
Rails.logger.info " Rendering partial: spree/page_blocks/products/razorpay_affordability/razorpay_affordability"
|
|
64
64
|
result = view_context.render partial: 'spree/page_blocks/products/razorpay_affordability/razorpay_affordability',
|
|
65
65
|
locals: locals.merge(block: self, page_block: self)
|
|
66
|
-
Rails.logger.info "
|
|
66
|
+
Rails.logger.info " Render successful, output length: #{result.to_s.length}"
|
|
67
67
|
result
|
|
68
68
|
rescue ActionView::MissingTemplate => e
|
|
69
|
-
Rails.logger.error "
|
|
69
|
+
Rails.logger.error " Missing template: #{e.message}"
|
|
70
70
|
''
|
|
71
71
|
rescue => e
|
|
72
|
-
Rails.logger.error "
|
|
72
|
+
Rails.logger.error " Error rendering Razorpay Affordability block: #{e.message}"
|
|
73
73
|
"<div class='razorpay-affordability-error'>Error loading Razorpay Affordability Widget</div>".html_safe
|
|
74
74
|
end
|
|
75
75
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PaymentSessions
|
|
3
|
+
class Razorpay < PaymentSession
|
|
4
|
+
# external_id will store the razorpay_order_id
|
|
5
|
+
def razorpay_order_id
|
|
6
|
+
external_id
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Helper to extract the signature if we need it later
|
|
10
|
+
def razorpay_signature
|
|
11
|
+
external_data&.dig('razorpay_signature')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Mirrors Stripe's approach to safely generate the Spree::Payment
|
|
15
|
+
def find_or_create_payment!(razorpay_payment_id)
|
|
16
|
+
return unless persisted?
|
|
17
|
+
return payment if payment.present?
|
|
18
|
+
|
|
19
|
+
# Create the Spree::Payment record in the checkout state
|
|
20
|
+
order.payments.create!(
|
|
21
|
+
amount: amount,
|
|
22
|
+
payment_method: payment_method,
|
|
23
|
+
response_code: razorpay_payment_id,
|
|
24
|
+
source: create_source(razorpay_payment_id), # Optional: if you still use a source model
|
|
25
|
+
state: 'checkout'
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Optional: Only needed if you want to keep using your custom source model
|
|
32
|
+
def create_source(payment_id)
|
|
33
|
+
::Spree::RazorpayCheckout.create!(
|
|
34
|
+
order_id: order.id,
|
|
35
|
+
razorpay_order_id: razorpay_order_id,
|
|
36
|
+
razorpay_payment_id: payment_id,
|
|
37
|
+
payment_method: payment_method.name,
|
|
38
|
+
status: 'captured'
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -6,9 +6,17 @@ module Razorpay
|
|
|
6
6
|
def create(order_id)
|
|
7
7
|
@order = Spree::Order.find_by(id: order_id)
|
|
8
8
|
raise "Order not found" unless order
|
|
9
|
+
|
|
10
|
+
Razorpay.headers = {
|
|
11
|
+
"Content-Type" => "application/json",
|
|
12
|
+
"Accept" => "application/json"
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
params = order_create_params
|
|
10
16
|
Rails.logger.info "Razorpay::Order.create Params: #{params.inspect}"
|
|
11
|
-
|
|
17
|
+
|
|
18
|
+
razorpay_order = Razorpay::Order.create(params.to_json)
|
|
19
|
+
|
|
12
20
|
if razorpay_order.try(:id).present?
|
|
13
21
|
log_order_in_db(razorpay_order.id)
|
|
14
22
|
return [razorpay_order.id, params[:amount]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="alert alert-info">
|
|
2
2
|
<p class="mb-0">
|
|
3
3
|
To find your <strong>Key</strong> and <strong>Key Secret</strong>, go to the
|
|
4
|
-
<%= external_link_to 'Razorpay dashboard', 'https://dashboard.razorpay.com/app/website-app-settings/api-keys', class: 'alert-link' %>
|
|
4
|
+
<%= external_link_to 'Razorpay dashboard', 'https://dashboard.razorpay.com/app/website-app-settings/api-keys', class: 'alert-link' %><br><%= external_link_to 'Create Webhook', 'https://dashboard.razorpay.com/app/website-app-settings/webhooks', class: 'alert-link' %> and mark all checkboxes in <strong>Active Events.</strong>
|
|
5
5
|
</p>
|
|
6
6
|
</div>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<%= payment_method_icon_tag 'visa', class: 'm-1' %>
|
|
7
7
|
<%= payment_method_icon_tag 'master', class: 'm-1' %>
|
|
8
8
|
<%= payment_method_icon_tag 'google_pay', class: 'm-1' %>
|
|
9
|
+
<%= payment_method_icon_tag 'apple_pay', class: 'm-1' %>
|
|
9
10
|
<%= payment_method_icon_tag 'amazon', class: 'm-1' %>
|
|
10
11
|
<%= payment_method_icon_tag 'upi', class: 'm-1' %>
|
|
11
12
|
</div>
|