Skip to content

Commit

Permalink
WIP -- stripe checkout & wechatpay
Browse files Browse the repository at this point in the history
Status:

* relevant commands have been spiked out but still need to be tested
* charges_controller has been spiked out to use the stripe checkout flow
* TODO: wire stripe checkout succeeded/failed commands to a webhook controller (https://stripe.com/docs/payments/checkout/fulfill-orders)
* TODO: write tests
  • Loading branch information
Betsy Haibel committed Oct 20, 2021
1 parent b56bd59 commit 67abbb4
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 170 deletions.
132 changes: 0 additions & 132 deletions app/commands/money/charge_customer.rb

This file was deleted.

126 changes: 126 additions & 0 deletions app/commands/money/start_stripe_checkout.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# frozen_string_literal: true

# Copyright 2019 Matthew B. Gray
# Copyright 2019 AJ Esler
# Copyright 2021 DisCon III
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Money::StartStripeCheckout creates a pending charge record against a User for the Stripe integrations
# and sets them up to check out with Stripe.
# Truthy returns mean the user can be safely sent on to the checkout flow, otherwise check #errors for failure details.
class Money::StartStripeCheckout
attr_reader :reservation, :user, :charge_amount, :charge, :amount_owed, :success_url, :failure_url

def initialize(reservation:, user:, amount_owed:, success_url:, cancel_url:, charge_amount: nil)
@reservation = reservation
@user = user
@charge_amount = charge_amount || amount_owed
@amount_owed = amount_owed
@success_url = success_url
@failure_url = failure_url
end

def call
setup_stripe_customer

check_charge_amount unless errors.any?
create_checkout_session unless errors.any?

charge_state_params = if errors.any?
{
state: ::Charge::STATE_FAILED,
comment: error_message,
}
else
{
state: ::Charge::STATE_PENDING,
comment: "Pending stripe payment",
}
end

@charge = ::Charge.stripe.create!({
user: user,
reservation: reservation,
stripe_id: @checkout_session.id,
amount: charge_amount,
}.merge(charge_state_params))

checkout_url.present?
end

def error_message
errors.to_sentence
end

def errors
@errors ||= []
end

def checkout_url
@checkout_session['url']
end

private

def check_charge_amount
if !charge_amount.present?
errors << "charge amount is missing"
end
if charge_amount <= 0
errors << "amount must be more than 0 cents"
end
if charge_amount > amount_owed
errors << "refusing to overpay for reservation"
end
end

def create_checkout_session
@checkout_session = Stripe::Checkout::Session.create({
line_items: [{
currency: ENV.fetch('STRIPE_CURRENCY'),
amount: charge_amount.cents,
name: reservation.membership.display_name,
},
}],
payment_method_types: [
'card',
'wechat_pay',
],
mode: 'payment',
success_url: success_url,
cancel_url: cancel_url,
})
end

def setup_stripe_customer
if !user.stripe_id.present?
stripe_customer = Stripe::Customer.create(email: user.email)
user.update!(stripe_id: stripe_customer.id)
end
rescue Stripe::StripeError => e
errors << e.message.to_s
@charge.stripe_response = json_to_hash(e.response)
@charge.comment = "Failed to setup customer - #{e.message}"
end

def json_to_hash(obj)
JSON.parse(obj.to_json)
rescue
{}
end

def fully_paid?
@charge.successful? && (amount_owed - charge_amount) <= 0
end
end
53 changes: 53 additions & 0 deletions app/commands/money/stripe_checkout_failed.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

# Copyright 2021 DisCon III
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Money::StripeCheckoutFailed updates the Charge record associated with that checkout session to indicate that the payment failed.
# Truthy returns mean that everything updated correctly, otherwise check #errors for failure details.
class Money::StripeCheckoutFailed
attr_reader :charge, :stripe_checkout_session

def initialize(charge:, stripe_checkout_session:)
@charge = charge
@stripe_checkout_session = stripe_checkout_session
end

def call
reservation.transaction do
charge.state == ::Charge::FAILED
charge.stripe_response = json_to_hash(stripe_checkout_session)
charge.comment = "Stripe checkout failed."
charge.save!
end

return charge
end

def error_message
errors.to_sentence
end

def errors
@errors ||= []
end

private

def json_to_hash(obj)
JSON.parse(obj.to_json)
rescue
{}
end
end
83 changes: 83 additions & 0 deletions app/commands/money/stripe_checkout_succeeded.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

# Copyright 2021 DisCon III
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Money::StripeCheckoutSucceeded updates the Charge record associated with that checkout session to indicate that the payment succeeded.
# Truthy returns mean that everything updated correctly, otherwise check #errors for failure details.
class Money::StripeCheckoutSucceeded
attr_reader :charge, :stripe_checkout_session

def initialize(charge:, stripe_checkout_session:)
@charge = charge
@stripe_checkout_session = stripe_checkout_session
end

def call
reservation.transaction do
charge.state == ::Charge::SUCCESSFUL
charge.stripe_response = json_to_hash(stripe_checkout_session)
charge.comment = ChargeDescription.new(charge).for_users
charge.save!
if fully_paid?
reservation.update!(state: Reservation::PAID)
else
reservation.update!(state: Reservation::INSTALMENT)
end
end

trigger_payment_mailer

return charge
end

def error_message
errors.to_sentence
end

def errors
@errors ||= []
end

private

def json_to_hash(obj)
JSON.parse(obj.to_json)
rescue
{}
end

def fully_paid?
reservation.charges.successful.sum(&:amount_cents) >= reservation.membership.price_cents
end

def outstanding_amount
raise "todo"
end

def trigger_payment_mailer
if charge.reservation.instalment?
PaymentMailer.instalment(
user: current_user,
charge: charge,
outstanding_amount: (outstanding_amount - charge.amount).format(with_currency: true)
).deliver_later
else
PaymentMailer.paid(
user: current_user,
charge: charge,
).deliver_later
end
end
end
Loading

0 comments on commit 67abbb4

Please sign in to comment.