Add stripe_refund.* publishing
Co-authored-by: Clarissa Lima Borges <clarissa@commitchange.com>
This commit is contained in:
parent
cced71f52e
commit
2c791b920c
14 changed files with 836 additions and 73 deletions
|
@ -567,7 +567,6 @@ AllCops:
|
||||||
- 'spec/lib/format/currency_spec.rb'
|
- 'spec/lib/format/currency_spec.rb'
|
||||||
- 'spec/lib/format/url_spec.rb'
|
- 'spec/lib/format/url_spec.rb'
|
||||||
- 'spec/lib/format/dedication_spec.rb'
|
- 'spec/lib/format/dedication_spec.rb'
|
||||||
- 'spec/lib/insert/insert_refunds_spec.rb'
|
|
||||||
- 'spec/lib/insert/insert_recurring_donation_spec.rb'
|
- 'spec/lib/insert/insert_recurring_donation_spec.rb'
|
||||||
- 'spec/lib/insert/insert_charge_spec.rb'
|
- 'spec/lib/insert/insert_charge_spec.rb'
|
||||||
- 'spec/lib/insert/insert_disputes_spec.rb'
|
- 'spec/lib/insert/insert_disputes_spec.rb'
|
||||||
|
@ -751,3 +750,6 @@ Style/PreferredHashMethods:
|
||||||
Style/LambdaCall:
|
Style/LambdaCall:
|
||||||
# jbuilder uses this a lot so we want it.
|
# jbuilder uses this a lot so we want it.
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
RSpec/MultipleMemoizedHelpers:
|
||||||
|
Max: 15
|
||||||
|
|
|
@ -187,6 +187,18 @@ class ObjectEventListener < ApplicationListener
|
||||||
enqueue_transmissions_to_webhooks(event)
|
enqueue_transmissions_to_webhooks(event)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.stripe_transaction_refund_created(event)
|
||||||
|
enqueue_transmissions_to_webhooks(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.stripe_transaction_refund_updated(event)
|
||||||
|
enqueue_transmissions_to_webhooks(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.stripe_transaction_refund_deleted(event)
|
||||||
|
enqueue_transmissions_to_webhooks(event)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def self.enqueue_transmissions_to_webhooks(event)
|
def self.enqueue_transmissions_to_webhooks(event)
|
||||||
|
|
|
@ -23,6 +23,8 @@ class Charge < ApplicationRecord
|
||||||
belongs_to :donation
|
belongs_to :donation
|
||||||
belongs_to :payment
|
belongs_to :payment
|
||||||
|
|
||||||
|
has_one :stripe_charge, through: :payment
|
||||||
|
|
||||||
scope :paid, -> { where(status: %w[available pending disbursed]) }
|
scope :paid, -> { where(status: %w[available pending disbursed]) }
|
||||||
scope :not_paid, -> { where(status: [nil, 'failed']) }
|
scope :not_paid, -> { where(status: [nil, 'failed']) }
|
||||||
scope :available, -> { where(status: 'available') }
|
scope :available, -> { where(status: 'available') }
|
||||||
|
|
|
@ -21,7 +21,8 @@ class Payment < ApplicationRecord
|
||||||
has_many :payment_payouts
|
has_many :payment_payouts
|
||||||
has_many :charges
|
has_many :charges
|
||||||
|
|
||||||
has_one :subtransaction_payment
|
|
||||||
|
has_one :stripe_charge
|
||||||
|
|
||||||
has_one :subtransaction, through: :subtransaction_payment
|
has_one :subtransaction, through: :subtransaction_payment
|
||||||
has_one :trx, through: :subtransaction_payment
|
has_one :trx, through: :subtransaction_payment
|
||||||
|
|
|
@ -10,6 +10,10 @@ class StripeCharge < ApplicationRecord
|
||||||
|
|
||||||
delegate :currency, to: :nonprofit
|
delegate :currency, to: :nonprofit
|
||||||
|
|
||||||
|
def stripe_id
|
||||||
|
payment.charge.stripe_charge_id
|
||||||
|
end
|
||||||
|
|
||||||
concerning :JBuilder do # rubocop:disable Metrics/BlockLength
|
concerning :JBuilder do # rubocop:disable Metrics/BlockLength
|
||||||
included do
|
included do
|
||||||
setup_houid :stripechrg
|
setup_houid :stripechrg
|
||||||
|
@ -35,7 +39,7 @@ class StripeCharge < ApplicationRecord
|
||||||
|
|
||||||
json.created payment.date.to_i
|
json.created payment.date.to_i
|
||||||
|
|
||||||
json.stripe_id payment.charge.stripe_charge_id
|
json.stripe_id stripe_id
|
||||||
|
|
||||||
json.type 'payment'
|
json.type 'payment'
|
||||||
|
|
||||||
|
|
107
app/models/stripe_refund.rb
Normal file
107
app/models/stripe_refund.rb
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
|
||||||
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
|
class StripeRefund < ApplicationRecord
|
||||||
|
include Model::SubtransactionPaymentable
|
||||||
|
belongs_to :payment
|
||||||
|
|
||||||
|
delegate :gross_amount, :net_amount, :fee_total, to: :payment
|
||||||
|
|
||||||
|
delegate :currency, to: :nonprofit
|
||||||
|
|
||||||
|
def stripe_id
|
||||||
|
payment.refund.stripe_refund_id
|
||||||
|
end
|
||||||
|
|
||||||
|
concerning :JBuilder do # rubocop:disable Metrics/BlockLength
|
||||||
|
included do
|
||||||
|
setup_houid :striperef
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_builder(*expand) # rubocop:disable Metrics/AbcSize
|
||||||
|
init_builder(*expand) do |json|
|
||||||
|
json.object 'stripe_transaction_refund'
|
||||||
|
json.gross_amount do
|
||||||
|
json.cents gross_amount
|
||||||
|
json.currency currency
|
||||||
|
end
|
||||||
|
|
||||||
|
json.net_amount do
|
||||||
|
json.cents net_amount
|
||||||
|
json.currency currency
|
||||||
|
end
|
||||||
|
|
||||||
|
json.fee_total do
|
||||||
|
json.cents fee_total
|
||||||
|
json.currency currency
|
||||||
|
end
|
||||||
|
|
||||||
|
json.created payment.date.to_i
|
||||||
|
|
||||||
|
json.stripe_id stripe_id
|
||||||
|
|
||||||
|
json.type 'payment'
|
||||||
|
|
||||||
|
json.add_builder_expansion :nonprofit, :supporter, :subtransaction
|
||||||
|
|
||||||
|
json.add_builder_expansion :trx, json_attribute: :transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_id
|
||||||
|
::Jbuilder.new do |json|
|
||||||
|
json.(self, :id)
|
||||||
|
json.object 'stripe_transaction_refund'
|
||||||
|
json.type 'payment'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish_created
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:stripe_transaction_refund_created,
|
||||||
|
to_event('stripe_transaction_refund.created', :nonprofit, :trx, :supporter, :subtransaction)
|
||||||
|
.attributes!
|
||||||
|
)
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:payment_created,
|
||||||
|
to_event('payment.created', :nonprofit, :trx, :supporter, :subtransaction)
|
||||||
|
.attributes!
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish_updated
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:stripe_transaction_refund_updated,
|
||||||
|
to_event('stripe_transaction_refund.updated', :nonprofit, :trx, :supporter, :subtransaction)
|
||||||
|
.attributes!
|
||||||
|
)
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:payment_updated,
|
||||||
|
to_event('payment.updated', :nonprofit, :trx, :supporter, :subtransaction)
|
||||||
|
.attributes!
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish_deleted
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:stripe_transaction_refund_deleted,
|
||||||
|
to_event('stripe_transaction_refund.deleted',
|
||||||
|
:nonprofit,
|
||||||
|
:trx,
|
||||||
|
:supporter,
|
||||||
|
:subtransaction).attributes!
|
||||||
|
)
|
||||||
|
Houdini.event_publisher.announce(
|
||||||
|
:payment_deleted,
|
||||||
|
to_event(
|
||||||
|
'payment.deleted',
|
||||||
|
:nonprofit,
|
||||||
|
:trx,
|
||||||
|
:supporter,
|
||||||
|
:subtransaction
|
||||||
|
).attributes!
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,7 +18,7 @@ class SubtransactionPayment < ApplicationRecord
|
||||||
OfflineTransactionDispute
|
OfflineTransactionDispute
|
||||||
OfflineTransactionRefund
|
OfflineTransactionRefund
|
||||||
StripeCharge
|
StripeCharge
|
||||||
StripeDispute
|
StripeRefund
|
||||||
]
|
]
|
||||||
|
|
||||||
delegate :gross_amount, :fee_total, :net_amount, to: :paymentable
|
delegate :gross_amount, :fee_total, :net_amount, to: :paymentable
|
||||||
|
|
9
db/migrate/20210513211918_create_stripe_refunds.rb
Normal file
9
db/migrate/20210513211918_create_stripe_refunds.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class CreateStripeRefunds < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :stripe_refunds, id: :string do |t|
|
||||||
|
t.references :payment, foreign_key: true, id: :string
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import type { HoudiniEvent } from "../../../common";
|
||||||
|
import type { CommonStripeTransactionPayment } from '.';
|
||||||
|
import type { PaymentAsId } from "..";
|
||||||
|
|
||||||
|
export interface RefundAsId extends PaymentAsId {
|
||||||
|
object: 'stripe_transaction_refund';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Refund = CommonStripeTransactionPayment & RefundAsId;
|
||||||
|
|
||||||
|
export type RefundCreated = HoudiniEvent<'stripe_transaction_refund.created', Refund>;
|
||||||
|
export type RefundUpdated = HoudiniEvent<'stripe_transaction_refund.updated', Refund>;
|
||||||
|
export type RefundDeleted = HoudiniEvent<'stripe_transaction_refund.deleted', Refund>;
|
|
@ -19,3 +19,4 @@ export type StripeTransactionDeleted = HoudiniEvent<'stripe_transaction.deleted'
|
||||||
|
|
||||||
export * from './Charge';
|
export * from './Charge';
|
||||||
export * from './Dispute';
|
export * from './Dispute';
|
||||||
|
export * from './Refund';
|
||||||
|
|
|
@ -66,6 +66,26 @@ module InsertRefunds
|
||||||
refund_row = Qx.update(:refunds).set(payment_id: payment_row['id']).ts.where(id: refund_row['id']).returning('*').execute.first
|
refund_row = Qx.update(:refunds).set(payment_id: payment_row['id']).ts.where(id: refund_row['id']).returning('*').execute.first
|
||||||
# Update original payment to increment its refund_total for any future refund attempts
|
# Update original payment to increment its refund_total for any future refund attempts
|
||||||
Qx.update(:payments).set("refund_total=refund_total + #{h['amount'].to_i}").ts.where(id: original_payment['id']).execute
|
Qx.update(:payments).set("refund_total=refund_total + #{h['amount'].to_i}").ts.where(id: original_payment['id']).execute
|
||||||
|
|
||||||
|
refund = Refund.find(refund_row['id'])
|
||||||
|
refund_payment = Payment.find(payment_row['id'])
|
||||||
|
|
||||||
|
stripe_transaction_charge = StripeCharge.find_by(payment: original_payment['id'])
|
||||||
|
stripe_transaction_refund = StripeRefund.new(payment: refund_payment)
|
||||||
|
transaction_id = stripe_transaction_charge.subtransaction_payment.subtransaction.transaction_id
|
||||||
|
transaction = Transaction.find(transaction_id)
|
||||||
|
|
||||||
|
transaction.amount += gross
|
||||||
|
refund_subtransaction_payment = transaction.subtransaction.subtransaction_payments.create(paymentable: stripe_transaction_refund)
|
||||||
|
|
||||||
|
stripe_transaction_refund.save!
|
||||||
|
refund_subtransaction_payment.save!
|
||||||
|
transaction.save!
|
||||||
|
|
||||||
|
transaction.publish_updated
|
||||||
|
transaction.publish_refunded
|
||||||
|
refund_subtransaction_payment.publish_created
|
||||||
|
|
||||||
# Send the refund receipts in a delayed job
|
# Send the refund receipts in a delayed job
|
||||||
Houdini.event_publisher.announce(:create_refund, Refund.find(refund_row['id']))
|
Houdini.event_publisher.announce(:create_refund, Refund.find(refund_row['id']))
|
||||||
{ 'payment' => payment_row, 'refund' => refund_row }
|
{ 'payment' => payment_row, 'refund' => refund_row }
|
||||||
|
|
9
spec/factories/stripe_refunds.rb
Normal file
9
spec/factories/stripe_refunds.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
|
||||||
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :stripe_refund do
|
||||||
|
payment { '' }
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,81 +4,392 @@
|
||||||
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe InsertRefunds, pending: true do
|
describe InsertRefunds do
|
||||||
# before(:all) do
|
let!(:nonprofit) { create(:nm_justice) }
|
||||||
# stripe_acct = VCR.use_cassette('InsertRefunds/stripe_acct'){Stripe::Account.create(managed: true, country: 'US', email: 'uzr@example.com').id}
|
let!(:supporter) { create(:supporter, nonprofit: nonprofit) }
|
||||||
# @supp = Psql.execute(Qexpr.new.insert('supporters', [{name: 'nnname'}])).first
|
|
||||||
# @payment = Psql.execute(Qexpr.new.insert('payments', [{supporter_id: @supp['id'], gross_amount: 100, fee_total: 33, net_amount: 67, refund_total: 0}]).returning('*')).first
|
let!(:payment) do
|
||||||
# @nonprofit = Psql.execute(Qexpr.new.insert('nonprofits', [{name: 'xxxx', stripe_account_id: stripe_acct}]).returning('*')).first
|
force_create(
|
||||||
# stripe_token = VCR.use_cassette('InsertRefunds/stripe_token'){Stripe::Token.create(card: StripeTestHelpers::Card_immediate).id}
|
:payment,
|
||||||
# stripe_charge = VCR.use_cassette('InsertRefunds/stripe_charge'){Stripe::Charge.create(amount: 10000, currency: 'usd', source: stripe_token, description: 'charge 1', destination: stripe_acct)}
|
gross_amount: 500,
|
||||||
# @charge = Qx.insert_into(:charges).values({
|
net_amount: 500 + CalculateFees.for_single_amount(500),
|
||||||
# amount: 100,
|
fee_total: CalculateFees.for_single_amount(500),
|
||||||
# stripe_charge_id: stripe_charge.id,
|
date: Time.zone.now,
|
||||||
# nonprofit_id: @nonprofit['id'],
|
nonprofit: nonprofit,
|
||||||
# payment_id: @payment['id'],
|
supporter: supporter,
|
||||||
# supporter_id: @supp['id']
|
refund_total: 0
|
||||||
# }).returning('*').timestamps.execute.first
|
)
|
||||||
# @refund_amount = 100
|
end
|
||||||
# @fees = CalculateFees.for_single_amount(100)
|
|
||||||
# end
|
let!(:charge) do
|
||||||
|
create(
|
||||||
|
:charge,
|
||||||
|
payment: payment,
|
||||||
|
stripe_charge_id: 'ch_s0m3th1ng',
|
||||||
|
nonprofit: nonprofit,
|
||||||
|
supporter: supporter,
|
||||||
|
amount: 500
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
trx = supporter.transactions.build(amount: 500)
|
||||||
|
trx.build_subtransaction(
|
||||||
|
subtransactable: StripeTransaction.new(amount: 500),
|
||||||
|
subtransaction_payments: [
|
||||||
|
build(:subtransaction_payment, paymentable: create(:stripe_charge, payment: payment))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
trx.save!
|
||||||
|
trx
|
||||||
|
end
|
||||||
|
|
||||||
describe '.with_stripe' do
|
describe '.with_stripe' do
|
||||||
context 'when invalid' do
|
context 'when invalid' do
|
||||||
it 'raises an error with an invalid charge' do
|
it 'raises an error with an invalid charge' do
|
||||||
bad_ch = @charge.merge('stripe_charge_id' => 'xxx')
|
charge.update(stripe_charge_id: 'xxx')
|
||||||
expect { InsertRefunds.with_stripe(bad_ch, 'amount' => 1) }.to raise_error(ParamValidation::ValidationError)
|
expect { described_class.with_stripe(charge, amount: 1) }.to raise_error(ParamValidation::ValidationError)
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets a failure message an error with an invalid amount' do
|
it 'sets a failure message an error with an invalid amount' do
|
||||||
bad_ch = @charge.merge('amount' => 0)
|
charge.update(amount: 0)
|
||||||
expect { InsertRefunds.with_stripe(bad_ch, 'amount' => 0) }.to raise_error(ParamValidation::ValidationError)
|
expect { described_class.with_stripe(charge, amount: 0) }.to raise_error(ParamValidation::ValidationError)
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns err if refund amount is greater than payment gross minus payment refund total' do
|
it 'returns err if refund amount is greater than payment gross minus payment refund total' do
|
||||||
new_payment = Qx.insert_into(:payments).values(gross_amount: 1000, fee_total: 0, net_amount: 1000, refund_total: 500).ts.returning('*').execute.first
|
expect { described_class.with_stripe(charge, 'amount' => 600) }.to raise_error(RuntimeError)
|
||||||
new_charge = @charge.merge('payment_id' => new_payment['id'])
|
|
||||||
expect { InsertRefunds.with_stripe(new_charge, 'amount' => 600) }.to raise_error(RuntimeError)
|
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when valid' do
|
context 'when valid' do
|
||||||
# before(:each) do
|
let(:result) { described_class.with_stripe(charge, 'amount' => 100) }
|
||||||
# @result = VCR.use_cassette 'InsertRefunds/result' do
|
|
||||||
# InsertRefunds.with_stripe(@charge, {'amount' => 100})
|
let(:retrieved_stripe_charge) { double }
|
||||||
# end
|
let(:stripe_charge_refunds) { double }
|
||||||
# @new_payment = Psql.execute("SELECT * FROM payments WHERE id=#{@payment['id']}").first
|
let(:created_stripe_charge_refund) { double }
|
||||||
# end
|
|
||||||
|
before do
|
||||||
|
allow(Stripe::Charge)
|
||||||
|
.to receive(:retrieve)
|
||||||
|
.with('ch_s0m3th1ng')
|
||||||
|
.and_return(retrieved_stripe_charge)
|
||||||
|
allow(retrieved_stripe_charge)
|
||||||
|
.to receive(:refunds)
|
||||||
|
.and_return(stripe_charge_refunds)
|
||||||
|
allow(stripe_charge_refunds)
|
||||||
|
.to receive(:create)
|
||||||
|
.with({ 'amount' => 100, 'refund_application_fee' => true, 'reverse_transfer' => true })
|
||||||
|
.and_return(created_stripe_charge_refund)
|
||||||
|
allow(created_stripe_charge_refund)
|
||||||
|
.to receive(:id)
|
||||||
|
.and_return('re_f@k3')
|
||||||
|
end
|
||||||
|
|
||||||
it 'sets the stripe refund id' do
|
it 'sets the stripe refund id' do
|
||||||
expect(@result['refund']['stripe_refund_id']).to match(/^re_/)
|
expect(result['refund']['stripe_refund_id']).to match(/^re_/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a negative payment for the refund with the gross amount' do
|
it 'creates a negative payment for the refund with the gross amount' do
|
||||||
expect(@result['payment']['gross_amount']).to eq(-@refund_amount)
|
expect(result['payment']['gross_amount']).to eq(-100)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a negative payment for the refund with the net amount' do
|
it 'creates a negative payment for the refund with the net amount' do
|
||||||
expect(@result['payment']['net_amount']).to eq(-@refund_amount + @fees)
|
expect(result['payment']['net_amount']).to eq(-109)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the payment_id on the refund' do
|
it 'updates the payment_id on the refund' do
|
||||||
expect(@result['refund']['payment_id']).to eq(@result['payment']['id'])
|
expect(result['refund']['payment_id']).to eq(result['payment']['id'])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'increments the payment refund total by the gross amount' do
|
it 'increments the payment refund total by the gross amount' do
|
||||||
expect(@new_payment['refund_total']).to eq(@refund_amount)
|
result
|
||||||
|
expect(payment.reload['refund_total']).to eq(100)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the payment supporter id' do
|
it 'sets the payment supporter id' do
|
||||||
expect(@result['payment']['supporter_id']).to eq(@supp['id'])
|
expect(result['payment']['supporter_id']).to eq(supporter['id'])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the payment fee_total as negative fees of the original payment' do
|
describe 'event publishing' do
|
||||||
expect(@result['payment']['fee_total']).to eq(CalculateFees.for_single_amount(@refund_amount))
|
let(:event_publisher) { double }
|
||||||
|
let(:expected_event) do
|
||||||
|
{
|
||||||
|
'data' => {
|
||||||
|
'object' => {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'fee_total' => {
|
||||||
|
'cents' => -9, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'gross_amount' => {
|
||||||
|
'cents' => -100, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'stripe_id' => kind_of(String),
|
||||||
|
'net_amount' => {
|
||||||
|
'cents' => -109, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'nonprofit' => {
|
||||||
|
'id' => nonprofit.id,
|
||||||
|
'name' => nonprofit.name,
|
||||||
|
'object' => 'nonprofit'
|
||||||
|
},
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'subtransaction' => {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'initial_amount' => {
|
||||||
|
'cents' => 500, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'net_amount' => {
|
||||||
|
'cents' => 432, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'payments' => [
|
||||||
|
{
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'type' => 'payment'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction' => match_houid('trx'),
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'supporter' => {
|
||||||
|
'anonymous' => supporter.anonymous,
|
||||||
|
'deleted' => supporter.deleted,
|
||||||
|
'id' => supporter.id,
|
||||||
|
'merged_into' => supporter.merged_into,
|
||||||
|
'name' => supporter.name,
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'supporter',
|
||||||
|
'organization' => supporter.organization,
|
||||||
|
'phone' => supporter.phone,
|
||||||
|
'supporter_addresses' => [kind_of(Numeric)]
|
||||||
|
},
|
||||||
|
'transaction' => {
|
||||||
|
'amount' => {
|
||||||
|
'cents' => 400, 'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('trx'),
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'transaction',
|
||||||
|
'subtransaction' => {
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'subtransaction_payments' => [
|
||||||
|
{
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'type' => 'payment'
|
||||||
|
}, {
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction_assignments' => []
|
||||||
|
},
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'id' => match_houid('objevt'),
|
||||||
|
'object' => 'object_event',
|
||||||
|
'type' => 'event_type'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_transaction_event) do
|
||||||
|
{
|
||||||
|
'data' => {
|
||||||
|
'object' => {
|
||||||
|
'amount' => {
|
||||||
|
'cents' => 400,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('trx'),
|
||||||
|
'nonprofit' => {
|
||||||
|
'id' => nonprofit.id,
|
||||||
|
'name' => nonprofit.name,
|
||||||
|
'object' => 'nonprofit'
|
||||||
|
},
|
||||||
|
'object' => 'transaction',
|
||||||
|
'subtransaction' => {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'initial_amount' => {
|
||||||
|
'cents' => 500,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'net_amount' => {
|
||||||
|
'cents' => 432,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'payments' => [
|
||||||
|
{
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'type' => 'payment'
|
||||||
|
}, {
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction' => match_houid('trx'),
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'subtransaction_payments' => [
|
||||||
|
{
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'fee_total' => {
|
||||||
|
'cents' => 41,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'gross_amount' => {
|
||||||
|
'cents' => 500,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'net_amount' => {
|
||||||
|
'cents' => 541,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'stripe_id' => 'ch_s0m3th1ng',
|
||||||
|
'subtransaction' => {
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction' => match_houid('trx'),
|
||||||
|
'type' => 'payment'
|
||||||
|
}, {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'fee_total' => {
|
||||||
|
'cents' => -9,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'gross_amount' => {
|
||||||
|
'cents' => -100,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'net_amount' => {
|
||||||
|
'cents' => -109,
|
||||||
|
'currency' => nonprofit.currency
|
||||||
|
},
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'stripe_id' => 're_f@k3',
|
||||||
|
'subtransaction' => {
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction' => match_houid('trx'),
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => {
|
||||||
|
'anonymous' => false,
|
||||||
|
'deleted' => false,
|
||||||
|
'id' => supporter.id,
|
||||||
|
'merged_into' => nil,
|
||||||
|
'name' => supporter.name,
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'supporter',
|
||||||
|
'organization' => nil,
|
||||||
|
'phone' => nil,
|
||||||
|
'supporter_addresses' => [kind_of(Numeric)]
|
||||||
|
},
|
||||||
|
'transaction_assignments' => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'id' => match_houid('objevt'),
|
||||||
|
'object' => 'object_event',
|
||||||
|
'type' => 'transaction.updated'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Houdini)
|
||||||
|
.to receive(:event_publisher)
|
||||||
|
.and_return(event_publisher)
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:payment_created, anything)
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:stripe_transaction_refund_created, anything)
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:transaction_refunded, anything)
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:transaction_updated, anything)
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:create_refund, anything)
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes that a transaction was updated' do
|
||||||
|
expected_transaction_event['type'] = 'transaction.updated'
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(:transaction_updated, expected_transaction_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes that a transaction was refunded' do
|
||||||
|
expected_transaction_event['type'] = 'transaction.refunded'
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(:transaction_refunded, expected_transaction_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes that a stripe_transaction_refund was created' do
|
||||||
|
expected_event['type'] = 'stripe_transaction_refund.created'
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(:stripe_transaction_refund_created, expected_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes that a payment was created' do
|
||||||
|
expected_event['type'] = 'payment.created'
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(:payment_created, expected_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes that a refund was created' do
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(:create_refund, kind_of(Refund))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
271
spec/models/stripe_refund_spec.rb
Normal file
271
spec/models/stripe_refund_spec.rb
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
|
||||||
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe StripeRefund, type: :model do
|
||||||
|
let!(:nonprofit) { create(:nm_justice) }
|
||||||
|
let!(:supporter) { create(:supporter, nonprofit: nonprofit) }
|
||||||
|
let!(:payment) do
|
||||||
|
force_create(
|
||||||
|
:payment,
|
||||||
|
gross_amount: 500,
|
||||||
|
net_amount: 400,
|
||||||
|
fee_total: 100,
|
||||||
|
date: Time.zone.now,
|
||||||
|
nonprofit: nonprofit,
|
||||||
|
supporter: supporter
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:stripe_charge) do
|
||||||
|
build(
|
||||||
|
:stripe_charge,
|
||||||
|
payment:
|
||||||
|
force_create(
|
||||||
|
:payment,
|
||||||
|
gross_amount: 500,
|
||||||
|
net_amount: 400,
|
||||||
|
fee_total: 100,
|
||||||
|
date: Time.zone.now,
|
||||||
|
nonprofit: nonprofit,
|
||||||
|
supporter: supporter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:stripe_transaction_refund) do
|
||||||
|
build(
|
||||||
|
:stripe_refund,
|
||||||
|
payment: payment
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:transaction) do
|
||||||
|
trx = supporter.transactions.build(amount: 500)
|
||||||
|
trx.build_subtransaction(
|
||||||
|
subtransactable: StripeTransaction.new(amount: 500),
|
||||||
|
subtransaction_payments: [
|
||||||
|
build(:subtransaction_payment, paymentable: stripe_charge),
|
||||||
|
build(:subtransaction_payment, paymentable: stripe_transaction_refund)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
trx.save!
|
||||||
|
trx
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:event_publisher) { double }
|
||||||
|
|
||||||
|
let(:expected_event) do
|
||||||
|
{
|
||||||
|
'data' => {
|
||||||
|
'object' => {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'fee_total' => { 'cents' => 100, 'currency' => nonprofit.currency },
|
||||||
|
'gross_amount' => { 'cents' => 500, 'currency' => nonprofit.currency },
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'stripe_id' => kind_of(String),
|
||||||
|
'net_amount' => { 'cents' => 400, 'currency' => nonprofit.currency },
|
||||||
|
'nonprofit' => {
|
||||||
|
'id' => nonprofit.id,
|
||||||
|
'name' => nonprofit.name,
|
||||||
|
'object' => 'nonprofit'
|
||||||
|
},
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'subtransaction' => {
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'initial_amount' => { 'cents' => 500, 'currency' => nonprofit.currency },
|
||||||
|
'net_amount' => { 'cents' => 800, 'currency' => nonprofit.currency },
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'payments' => [
|
||||||
|
{
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'type' => 'payment'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction' => match_houid('trx'),
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'supporter' => {
|
||||||
|
'anonymous' => supporter.anonymous,
|
||||||
|
'deleted' => supporter.deleted,
|
||||||
|
'id' => supporter.id,
|
||||||
|
'merged_into' => supporter.merged_into,
|
||||||
|
'name' => supporter.name,
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'supporter',
|
||||||
|
'organization' => supporter.organization,
|
||||||
|
'phone' => supporter.phone,
|
||||||
|
'supporter_addresses' => [kind_of(Numeric)]
|
||||||
|
},
|
||||||
|
'transaction' => {
|
||||||
|
'amount' => { 'cents' => 500, 'currency' => nonprofit.currency },
|
||||||
|
'created' => kind_of(Numeric),
|
||||||
|
'id' => match_houid('trx'),
|
||||||
|
'nonprofit' => nonprofit.id,
|
||||||
|
'object' => 'transaction',
|
||||||
|
'subtransaction' => {
|
||||||
|
'id' => match_houid('stripetrx'),
|
||||||
|
'object' => 'stripe_transaction',
|
||||||
|
'type' => 'subtransaction'
|
||||||
|
},
|
||||||
|
'subtransaction_payments' => [
|
||||||
|
{
|
||||||
|
'id' => match_houid('stripechrg'),
|
||||||
|
'object' => 'stripe_transaction_charge',
|
||||||
|
'type' => 'payment'
|
||||||
|
}, {
|
||||||
|
'id' => match_houid('striperef'),
|
||||||
|
'object' => 'stripe_transaction_refund',
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'supporter' => supporter.id,
|
||||||
|
'transaction_assignments' => []
|
||||||
|
},
|
||||||
|
'type' => 'payment'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'id' => match_houid('objevt'),
|
||||||
|
'object' => 'object_event',
|
||||||
|
'type' => 'event_type'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Houdini)
|
||||||
|
.to receive(:event_publisher)
|
||||||
|
.and_return(event_publisher)
|
||||||
|
force_create(:refund, payment: payment, disbursed: true, stripe_refund_id: 'some_id')
|
||||||
|
transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'stripe transaction refund' do
|
||||||
|
subject { stripe_transaction_refund }
|
||||||
|
|
||||||
|
it do
|
||||||
|
is_expected
|
||||||
|
.to have_attributes(
|
||||||
|
nonprofit: an_instance_of(Nonprofit),
|
||||||
|
id: match_houid('striperef')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_persisted }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.to_builder' do
|
||||||
|
subject { JSON.parse(stripe_transaction_refund.to_builder.target!) }
|
||||||
|
|
||||||
|
it do
|
||||||
|
is_expected
|
||||||
|
.to match_json(
|
||||||
|
{
|
||||||
|
object: 'stripe_transaction_refund',
|
||||||
|
nonprofit: nonprofit.id,
|
||||||
|
supporter: supporter.id,
|
||||||
|
id: match_houid('striperef'),
|
||||||
|
stripe_id: kind_of(String),
|
||||||
|
type: 'payment',
|
||||||
|
fee_total: { cents: 100, currency: nonprofit.currency },
|
||||||
|
net_amount: { cents: 400, currency: nonprofit.currency },
|
||||||
|
gross_amount: { cents: 500, currency: nonprofit.currency },
|
||||||
|
created: kind_of(Numeric),
|
||||||
|
subtransaction: { id: match_houid('stripetrx'), object: 'stripe_transaction', type: 'subtransaction' },
|
||||||
|
transaction: match_houid('trx')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.publish_created' do
|
||||||
|
before do
|
||||||
|
expected_event['type'] = 'stripe_transaction_refund.created'
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:payment_created, anything)
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_created,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'announces stripe_transaction_refund.created event' do
|
||||||
|
stripe_transaction_refund.publish_created
|
||||||
|
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_created,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.publish_updated' do
|
||||||
|
before do
|
||||||
|
expected_event['type'] = 'stripe_transaction_refund.updated'
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:payment_updated, anything)
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_updated,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'announces stripe_transaction_refund.updated event' do
|
||||||
|
stripe_transaction_refund.publish_updated
|
||||||
|
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_updated,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.publish_deleted' do
|
||||||
|
before do
|
||||||
|
expected_event['type'] = 'stripe_transaction_refund.deleted'
|
||||||
|
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(:payment_deleted, anything)
|
||||||
|
allow(event_publisher)
|
||||||
|
.to receive(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_deleted,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'announces stripe_transaction_refund.deleted event' do
|
||||||
|
stripe_transaction_refund.publish_deleted
|
||||||
|
|
||||||
|
expect(event_publisher)
|
||||||
|
.to have_received(:announce)
|
||||||
|
.with(
|
||||||
|
:stripe_transaction_refund_deleted,
|
||||||
|
expected_event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue