Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Get confirmation from user for redonation within 10 minutes'
Browse files Browse the repository at this point in the history
  • Loading branch information
shivashankar-ror committed Apr 17, 2019
1 parent b2dd73d commit 0dcf9ea
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 7 deletions.
17 changes: 12 additions & 5 deletions app/controllers/api/payment/braintree_controller.rb
Expand Up @@ -16,15 +16,22 @@ def webhook
end

def one_click
@result = client::OneClick.new(unsafe_params, cookies.signed[:payment_methods]).run
unless @result.success?
@errors = client::ErrorProcessing.new(@result, locale: locale).process
render status: :unprocessable_entity, errors: @errors
end
member = Member.find_by_email(params[:user][:email])
@result = client::OneClick.new(unsafe_params, cookies.signed[:payment_methods], member).run

render status: :unprocessable_entity, errors: oneclick_payment_errors unless @result.success?
end

private

def oneclick_payment_errors
if @result.class == Braintree::ErrorResult
client::ErrorProcessing.new(@result, locale: locale).process
else
@result.errors
end
end

def payment_options
{
nonce: unsafe_params[:payment_method_nonce],
Expand Down
41 changes: 41 additions & 0 deletions app/lib/payment_processor/braintree/one_click.rb
@@ -1,6 +1,28 @@
# frozen_string_literal: true

module PaymentProcessor::Braintree
class DuplicateDonationError < StandardError
def message
I18n.t('fundraiser.oneclick.duplicate_donation')
end
end

class DuplicateDonationResponse
attr_accessor :errors, :message, :params
attr_reader :immediate_redonation

def initialize(errors: [], message: '', params: {})
@errors = errors
@message = message
@params = params
@immediate_redonation = true
end

def success?
@errors.empty?
end
end

class OneClick
attr_reader :params, :payment_options

Expand All @@ -11,6 +33,10 @@ def initialize(params, cookied_payment_methods, member = nil)
end

def run
# TODO: On the second attempt (if the member consents to duplicate donation), post with the same parameters
# but also params[:allow_duplicate] = true
return duplicate_donation_error_response if duplicate_donation && !payment_options.params[:allow_duplicate]

sale = make_payment
if sale.success?
action = create_action(extra_fields(sale))
Expand All @@ -20,6 +46,21 @@ def run
sale
end

def duplicate_donation
resources = (payment_options.recurring? ? 'subscriptions' : 'transactions')
# Check if there are any transactions/subscriptions for the customer,
# within 10 minutes, with the same amount
!@member.customer.send(resources)
.where('created_at > ? AND amount = ? AND page_id = ?',
10.minutes.ago, payment_options.params[:payment][:amount], params[:page_id])
.empty?
end

def duplicate_donation_error_response
error = DuplicateDonationError.new
DuplicateDonationResponse.new(errors: [error], message: error.message, params: @params)
end

def extra_fields(sale)
if payment_options.recurring?
return {
Expand Down
1 change: 1 addition & 0 deletions app/views/api/payment/braintree/one_click.json.jbuilder
Expand Up @@ -5,4 +5,5 @@ unless @result.success?
json.params @result.params
json.errors @result.errors
json.message @result.message
json.immediate_redonation @result.try(:immediate_redonation)
end
2 changes: 2 additions & 0 deletions config/locales/member_facing.de.yml
Expand Up @@ -94,6 +94,8 @@ de:
credit_card_payment_method: "%{card_type} gültig bis %{last_four_digits}"
paypal_payment_method: "Paypal (%{email})"
new_payment_method: "Zahlungsmethode hinzufügen"
duplicate_donation: "Sie haben vor wenigen Minuten schon einmal gespendet. Sind Sie sicher, dass sie noch einmal spenden möchten? Dann klicken Sie bitte hier: %{link}. Wir möchten nur sicher gehen, dass Sie nicht versehentlich doppelt spenden.
Vielen Dank für Ihre Unterstützung!"
debit:
recurring: "Sie machen eine monatliche Spende von %{amount} an SumOfUs"
one_time: "Sie spenden %{amount} an SumOfUs"
Expand Down
2 changes: 2 additions & 0 deletions config/locales/member_facing.en.yml
Expand Up @@ -101,6 +101,8 @@ en:
credit_card_payment_method: "%{card_type} ending in %{last_four_digits}"
paypal_payment_method: "Paypal (%{email})"
new_payment_method: "Add payment method"
duplicate_donation: "You've just made a donation a few minutes ago. Are you sure, you want to donate again? If yes, please click here %{link}. We're doing this to make sure you don't accidentally donate twice.
Thanks so much for everything you do!"
debit:
recurring: "You are setting up a monthly donation of %{amount}"
one_time: "You are donating %{amount}"
Expand Down
2 changes: 2 additions & 0 deletions config/locales/member_facing.es.yml
Expand Up @@ -99,6 +99,8 @@ es:
credit_card_payment_method: "%{card_type} terminada en %{last_four_digits}"
paypal_payment_method: "Paypal (%{email})"
new_payment_method: "Agregar método de pago"
duplicate_donation: "You've just made a donation a few minutes ago. Are you sure, you want to donate again? If yes, please click here %{link}. We're doing this to make sure you don't accidentally donate twice.
Thanks so much for everything you do!"
debit:
recurring: "Estás estableciendo una donación mensual de %{amount}"
one_time: "Estás donando %{amount}"
Expand Down
2 changes: 2 additions & 0 deletions config/locales/member_facing.fr.yml
Expand Up @@ -85,6 +85,8 @@ fr:
credit_card_payment_method: "%{card_type} finissant par %{last_four_digits}"
paypal_payment_method: "Paypal (%{email})"
new_payment_method: "Ajouter un mode de paiement"
duplicate_donation: "Vous avez fait un don il y a quelques minutes. Êtes-vous sur de vouloir faire un nouveau don ? Si oui, veuillez cliquer ici : %{link}. Nous souhaitons simplement nous assurer que vous ne faites pas un double don par erreur.
Merci pour tout ce que vous faites !"
debit:
recurring: "Vous souhaitez faire un don mensuel de %{amount} à SumOfUs."
one_time: "Vous souhaitez donner %{amount} à SumOfUs."
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 79 additions & 2 deletions spec/requests/api/braintree/braintree_spec.rb
Expand Up @@ -16,7 +16,7 @@
allow(FundingCounter).to receive(:update)
end

describe 'making multiple transactions on the same page' do
describe 'making multiple transactions on the same page with after 10 mins' do
subject do
body = {
payment: {
Expand All @@ -33,7 +33,9 @@
page_id: page.id
}
post api_payment_braintree_one_click_path(page.id), params: body
post api_payment_braintree_one_click_path(page.id), params: body
Timecop.freeze(Time.now + 11.minutes) do
post api_payment_braintree_one_click_path(page.id), params: body
end
end

it 'creates an action and a transaction for each payment' do
Expand All @@ -47,6 +49,81 @@
end
end

describe 'making multiple transactions on the same page and amount within 10 mins' do
subject do
body = {
payment: {
amount: 2.00,
payment_method_id: payment_method.id,
currency: 'GBP',
recurring: false
},
user: {
form_id: form.id,
email: 'test@example.com',
name: 'John Doe'
},
page_id: page.id
}
post api_payment_braintree_one_click_path(page.id), params: body
Timecop.freeze(Time.now + (1..9).to_a.sample.minutes) do
post api_payment_braintree_one_click_path(page.id), params: body
end
end

it 'should not allow duplicate donation' do
VCR.use_cassette('express_donation_multiple_transactions') do
expect(Action.all.count).to eq 0
expect(Payment::Braintree::Transaction.all.count).to eq 0
subject
expect(Action.all.count).to eq 1
expect(Payment::Braintree::Transaction.all.count).to eq 1

expect(response.status).to eq 422
expect(json_hash['message']).to include(
"You've just made a donation a few minutes ago. Are you sure, you want to donate again?"
)
end
end
end

describe 'making duplicate donation for same page within 10 mins and duplicate attribute' do
subject do
body = {
payment: {
amount: 2.00,
payment_method_id: payment_method.id,
currency: 'GBP',
recurring: false
},
user: {
form_id: form.id,
email: 'test@example.com',
name: 'John Doe'
},
page_id: page.id
}
post api_payment_braintree_one_click_path(page.id), params: body

Timecop.freeze(Time.now + (1..9).to_a.sample.minutes) do
body[:allow_duplicate] = true
post api_payment_braintree_one_click_path(page.id), params: body
end
end

it 'should allow duplicate donation' do
VCR.use_cassette('express_donation_multiple_transactions') do
expect(Action.all.count).to eq 0
expect(Payment::Braintree::Transaction.all.count).to eq 0
subject
expect(Action.all.count).to eq 2
expect(Payment::Braintree::Transaction.all.count).to eq 2

expect(response.status).to eq 200
end
end
end

describe 'subscription' do
before do
VCR.use_cassette('braintree_express_donation_subscription') do
Expand Down

0 comments on commit 0dcf9ea

Please sign in to comment.