Add stripe_refund.* publishing

Co-authored-by: Clarissa Lima Borges <clarissa@commitchange.com>
This commit is contained in:
Eric Schultz 2021-05-13 14:18:30 -05:00 committed by Eric Schultz
parent cced71f52e
commit 2c791b920c
14 changed files with 836 additions and 73 deletions

View file

@ -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

View file

@ -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)

View file

@ -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') }

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View 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

View file

@ -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>;

View file

@ -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';

View file

@ -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 }

View 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

View file

@ -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

View 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