-
Notifications
You must be signed in to change notification settings - Fork 438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add support for Stripe Payment Element #409
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module SpreeGateway | ||
module Order | ||
module PaymentsDecorator | ||
|
||
def unprocessed_payments | ||
payments.select(&:checkout_or_intent?) | ||
end | ||
|
||
end | ||
end | ||
end | ||
|
||
::Spree::Order::Payments.prepend(::SpreeGateway::Order::PaymentsDecorator) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module SpreeGateway | ||
module Payment | ||
module ProcessingDecorator | ||
|
||
def create_intent! | ||
process_create_intent | ||
end | ||
|
||
private | ||
|
||
def process_create_intent | ||
started_creating_intent! | ||
gateway_action(nil, :create_intent, :intent_created) | ||
end | ||
|
||
end | ||
end | ||
end | ||
|
||
::Spree::Payment.prepend(::SpreeGateway::Payment::ProcessingDecorator) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class AddIntentIdToPayments < ActiveRecord::Migration[5.2] | ||
def change | ||
add_column :spree_payments, :intent_id, :string | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
module Spree | ||
module Api | ||
module V2 | ||
module Storefront | ||
class WebhooksController < ::Spree::Api::V2::BaseController | ||
|
||
def stripe | ||
require 'stripe' | ||
Stripe.api_key = ::Spree::Gateway::StripeElementsGateway&.active.first.get_preference(:secret_key) | ||
endpoint_secret = ::Spree::Gateway::StripeElementsGateway&.active.first.get_preference(:endpoint_secret) | ||
|
||
payload = request.body.read | ||
event = nil | ||
|
||
begin | ||
event = Stripe::Event.construct_from( | ||
JSON.parse(payload, symbolize_names: true) | ||
) | ||
rescue JSON::ParserError => e | ||
# Invalid payload | ||
puts "Webhook error while parsing basic request. #{e.message})" | ||
status 400 | ||
return | ||
end | ||
# Check if webhook signing is configured. | ||
if endpoint_secret | ||
# Retrieve the event by verifying the signature using the raw body and secret. | ||
signature = request.env['HTTP_STRIPE_SIGNATURE']; | ||
begin | ||
event = Stripe::Webhook.construct_event( | ||
payload, signature, endpoint_secret | ||
) | ||
rescue Stripe::SignatureVerificationError | ||
puts "Webhook signature verification failed. #{err.message})" | ||
status 400 | ||
end | ||
end | ||
|
||
# Handle the event | ||
case event.type | ||
when 'payment_intent.succeeded' | ||
payment_intent = event.data.object # contains a Stripe::PaymentIntent | ||
puts "Payment for #{payment_intent['amount']} succeeded." | ||
|
||
# Find payment details (from stripe payment element) and payment in spree | ||
stripe_payment_method = Stripe::PaymentMethod.retrieve(payment_intent[:payment_method]) | ||
payment = Spree::Payment.find_by(intent_id: payment_intent['id']) | ||
|
||
if payment | ||
payment_method = payment.payment_method | ||
# Create source using payment details from stripe payment element | ||
if payment.source.blank? && payment_method.try(:payment_source_class) | ||
payment.source = payment_method.payment_source_class.create!({ | ||
gateway_payment_profile_id: stripe_payment_method.id, | ||
cc_type: stripe_payment_method.card.brand, | ||
month: stripe_payment_method.card.exp_month, | ||
year: stripe_payment_method.card.exp_year, | ||
last_digits: stripe_payment_method.card.last4, | ||
payment_method: payment_method | ||
}) | ||
end | ||
|
||
# Update payment to pending if authorised only, and completed if auto capture enabled | ||
if payment_intent['capture_method'] == "manual" | ||
payment.update!(state: "pending") | ||
else | ||
payment.update!(state: "completed") | ||
end | ||
|
||
# Update order status to complete | ||
order = payment.order | ||
order.next until cannot_make_transition?(order) | ||
end | ||
else | ||
puts "Unhandled event type: #{event.type}" | ||
end | ||
render json: { message: I18n.t('spree.stripe.response.success') }, status: :ok | ||
end | ||
|
||
private | ||
|
||
def cannot_make_transition?(order) | ||
order.complete? || order.errors.present? | ||
end | ||
|
||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ class Engine < Rails::Engine | |
|
||
config.autoload_paths += %W(#{config.root}/lib) | ||
|
||
initializer "spree.gateway.payment_methods", :after => "spree.register.payment_methods" do |app| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, good point |
||
config.after_initialize do |app| | ||
app.config.spree.payment_methods << Spree::Gateway::AuthorizeNet | ||
app.config.spree.payment_methods << Spree::Gateway::AuthorizeNetCim | ||
app.config.spree.payment_methods << Spree::Gateway::BalancedGateway | ||
|
@@ -44,6 +44,9 @@ def self.activate | |
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/spree_gateway/*_decorator*.rb')) do |c| | ||
Rails.application.config.cache_classes ? require(c) : load(c) | ||
end | ||
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/spree_gateway/**/*_decorator*.rb')) do |c| | ||
Rails.application.config.cache_classes ? require(c) : load(c) | ||
end | ||
Dir.glob(File.join(File.dirname(__FILE__), '../../lib/active_merchant/**/*_decorator*.rb')) do |c| | ||
Rails.application.config.cache_classes ? require(c) : load(c) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s| | |
|
||
s.add_dependency 'spree_core', '>= 3.7.0' | ||
s.add_dependency 'spree_extension' | ||
s.add_dependency 'stripe' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, so that's not already included? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I only added this to make manipulating the object coming back from Stripe via webhook easier. ActiveMerchant handles the rest of the Stripe <--> Spree logic, so was not previously included. Happy to add the current version number once we've got the other points 'locked down' (pun intended). |
||
|
||
s.add_development_dependency 'braintree', '~> 3.0.0' | ||
s.add_development_dependency 'rspec-activemodel-mocks' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we need this additional state related to intent, or if we could just save a
Payment
object for StripeElementsGateway only if the payment was successfully created?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if that needs to happen, this would probably be better of in core. This will be a breaking change though for other payment gateways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rafalcymerys Yes, I was unsure if there was a way to avoid adding the extra 'intent' payment state. In the end I felt like it was significant enough to warrant a separate state as it may apply to other payment gateways, and seemed to make sense as a way to call the Stripe Gateway. If you were to remove the 'intent' state, how would you suggest calling the Stripe Gateway to create the payment intent prior to save? Would it be some sort of before_action related to payment? And would it go via the spree_gateway?