Skip to content

Commit

Permalink
add dispute submission, webhook handler
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-stripe committed Oct 10, 2017
1 parent f801a09 commit 1a5e5d0
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 16 deletions.
Binary file added app/assets/images/favicon.ico
Binary file not shown.
3 changes: 0 additions & 3 deletions app/assets/stylesheets/payouts.scss

This file was deleted.

14 changes: 7 additions & 7 deletions app/controllers/charges_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def create
flash[:error] = "No payment information submitted."
redirect_back(fallback_location: root_path) and return
end

# Check for a valid campaign ID
unless charge_params[:campaign] && Campaign.exists?(charge_params[:campaign])
flash[:error] = "The campaign you specified doesn't exist."
Expand All @@ -23,14 +23,14 @@ def create

# Convert the amount to cents
amount = (100 * charge_params[:amount].tr('$', '').to_r).to_i

# Create the charge with Stripe
charge = Stripe::Charge.create({
source: charge_params[:stripeToken],
amount: amount,
currency: "usd",
currency: "usd",
application_fee: amount/10, # Take a 10% application fee for the platform
destination: account_id,
destination: account_id,
metadata: { "name" => charge_params[:name], "campaign" => campaign.id }
}
)
Expand Down Expand Up @@ -65,14 +65,14 @@ def create
def show
begin
# Retrieve the charge from Stripe
@charge = Stripe::Charge.retrieve(id: params[:id], expand: ['application_fee'])
@charge = Stripe::Charge.retrieve(id: params[:id], expand: ['application_fee', 'dispute'])

# Validate that the user should be able to view this charge
check_destination(@charge)

# Get the campaign from the metadata on the charge object
@campaign = Campaign.find(@charge.metadata.campaign)

# Handle exceptions from Stripe
rescue Stripe::StripeError => e
flash[:error] = e.message
Expand Down Expand Up @@ -118,7 +118,7 @@ def destroy
rescue Stripe::StripeError => e
flash.now[:error] = e.message
redirect_to dashboard_path

# Handle other failures
rescue => e
flash.now[:error] = e.message
Expand Down
43 changes: 43 additions & 0 deletions app/controllers/disputes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class DisputesController < ApplicationController
before_action :authenticate_user!

def create
if dispute_params[:dispute_text].empty? || dispute_params[:dispute_id].empty?
flash[:error] = "Please provide supporting information about this dispute"
redirect_back(fallback_location: root_path) and return
end

begin
# Retrieve the account object for this user
account = Stripe::Account.retrieve(current_user.stripe_account)

# Retrieve the dispute
dispute = Stripe::Dispute.retrieve(dispute_params[:dispute_id])

# Add the dispute evidence
dispute.evidence.uncategorized_text = dispute_params[:dispute_text]
# Add dispute document if one exists
dispute.evidence.uncategorized_file = dispute_params[:dispute_document]
dispute.save

# Success, send back to the page
flash[:success] = "This dispute has been updated"
redirect_back(fallback_location: root_path) and return

# Handle exceptions from Stripe
rescue Stripe::StripeError => e
flash[:error] = e.message
redirect_to dashboard_path

# Handle any other exceptions
rescue => e
flash[:error] = e.message
redirect_to dashboard_path
end
end

private
def dispute_params
params.permit(:dispute_id, :dispute_text, :dispute_document)
end
end
17 changes: 16 additions & 1 deletion app/controllers/webhooks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def stripe
case event.type

#############
# Chargebacks
# Disputes
#############
when 'charge.dispute.created'
# The dispute that was created
Expand All @@ -44,6 +44,21 @@ def stripe
)
end

when 'charge.dispute.funds_reinstated'
# The dispute that was created
dispute = event.data.object

# Retrieve the charge related to this dispute
charge = Stripe::Charge.retrieve(dispute.charge)

# Create a transfer to the connected account to return the dispute fee
transfer = Stripe::Transfer.create(
amount: dispute.balance_transactions.second.net,
currency: "usd"
destination: charge.destination
)
end

# Something bad happened with the event or retrieving details from Stripe: probably log this.
rescue JSON::ParserError, Stripe::SignatureVerificationError, Stripe::StripeError => e
head :bad_request
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/disputes_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module DisputesHelper
end
2 changes: 1 addition & 1 deletion app/views/campaigns/_charges.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<span class="fa fa-undo"></span> Refunded
</span>
<% else %>
<%= link_to "Refund", charge_path(payment.source_transfer.source_transaction), method: :delete, data: { confirm: "Are you sure you want to refund this charge?", disable_with: "<i class='fa fa-spinner fa-spin'></i> Refunding charge..." }, class: "btn btn-sm btn-block btn-custom btn-danger" %>
<%= link_to "Refund", charge_path(payment.source_transfer.source_transaction.id), method: :delete, data: { confirm: "Are you sure you want to refund this charge?", disable_with: "<i class='fa fa-spinner fa-spin'></i> Refunding charge..." }, class: "btn btn-sm btn-block btn-custom btn-danger" %>
<% end %>
</td>
</tr>
Expand Down
85 changes: 85 additions & 0 deletions app/views/charges/_dispute.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<div class="topspace-lg">
<h2 class="text-center">Respond to this dispute</h2>
<div class="panel panel-default">
<div class="panel-body">
<form action="/disputes" method="POST" id="dispute-form">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label>Supporting information</label>
<textarea class="form-control input-lg" id="dispute_text" name="dispute_text"></textarea>
<div class="help-block">
Share any communication with the customer or feedback on why this donation shouldn't have been disputed
</div>
</div>
<div class="form-group">
<label>Supporting document</label>
<input type="file" id="file_upload">
<span class="help-block">Upload any supporting documentation (optional)</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn btn-lg btn-block btn-primary btn-custom submit" type="submit">Submit dispute information</button>
</div>
</div>
</div>
<%= hidden_field_tag :authenticity_token, form_authenticity_token -%>
<%= hidden_field_tag :dispute_id, @charge.dispute.id -%>
</form>
</div>
</div>
</div>
<script src="https://js.stripe.com/v3/"></script>
<script type="text/javascript">
var Stripe = Stripe("<%= ENV['PUBLISHABLE_KEY'] %>");

$(function() {
var $form = $('form');

$form.submit(function(event) {
// Prevent the form submission
event.preventDefault();

// If a dispute document is added, upload it client-side
if ($("#file_upload").val() !== "") {
// Disable the submit button to prevent repeated clicks:
$form.find('.submit').prop('disabled', true).html("<i class='fa fa-spinner fa-spin'></i> Submitting dispute information...");
// Remove any existing errors
$form.find('.alert-danger').remove();

var data = new FormData();
data.append('file', $('#file_upload')[0].files[0]);
data.append('purpose', 'dispute_evidence');

$.ajax({
url: 'https://uploads.stripe.com/v1/files',
data: data,
headers: {
'Authorization': 'Bearer ' + "<%= ENV['PUBLISHABLE_KEY'] %>"
},
cache: false,
contentType: false,
processData: false,
type: 'POST',
})
.done(function(response) {
// Insert the file id into the form so it gets submitted to the server:
$form.append($('<input type="hidden" name="dispute_document" />').val(response.id));
// Submit the form:
$form.get(0).submit();
})
.fail(function(response) {
$form.append("<div class='alert alert-danger'>"+response.responseJSON.error.message+"</div>");
$form.find('.submit').prop('disabled', false).text('Submit dispute information'); // Re-enable submission
});
}
else {
// Submit the form:
$form.get(0).submit();
}
});

});
</script>
16 changes: 14 additions & 2 deletions app/views/charges/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,26 @@
<% if @charge.refunded %>
<button class="btn btn-lg btn-block btn-custom btn-danger disabled">This charge has been refunded</button>
<% elsif @charge.dispute.present? %>
<div class="text-center text-danger">
This donation was disputed. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been withdrawn from your account.
<div class="text-center">
<% if @charge.dispute.status.eql?('needs_response') %>
<span class="label label-danger">Needs response</span>
This donation was disputed. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been withdrawn from your account.
<% elsif @charge.dispute.status.eql?('won') %>
<span class="label label-success">Won</span>
This dispute was won in your favor. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been returned to your account.
<% elsif @charge.dispute.status.eql?('lost') %>
<span class="label label-default">Lost</span>
The bank sided in favor of the donor. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been withdrawn from your account.
<% end %>
</div>
<% else %>
<%= link_to "Refund this donation", charge_path(@charge.id), method: :delete, data: { confirm: "Are you sure you want to refund this charge?", disable_with: "<i class='fa fa-spinner fa-spin'></i> Refunding charge..." }, class: "btn btn-lg btn-block btn-custom btn-danger" %>
<% end %>
</div>
</div>
<% if @charge.dispute.present? && (@charge.dispute.status.eql?('needs_response') || @charge.dispute.status.eql?('under_review')) %>
<%= render 'dispute' %>
<% end %>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= favicon_link_tag 'favicon.ico' %>
<%= yield(:header) %>
</head>

Expand Down
4 changes: 3 additions & 1 deletion app/views/payouts/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</thead>
<tbody>
<% @txns.auto_paging_each do |txn| %>
<% unless txn.type.eql?('transfer') && txn.source.method.eql?('standard') %>
<% unless txn.type.eql?('transfer') && txn.source.type.eql?('bank_account') %>
<tr>
<td>
<% if txn.type.eql?('payment') %>
Expand All @@ -50,6 +50,8 @@
Marketplace fee refund
<% elsif txn.type.eql?('transfer') && txn.source.method.eql?('instant') %>
Instant payout <%= txn.source.id %>
<% elsif txn.type.eql?('transfer') && txn.source.type.eql?('stripe_account') %>
<%= txn.description %>
<% else %>
<%= txn.type %>
<% end %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,4 @@
});
}
});
</script>
</script>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Rails.application.routes.draw do

devise_for :users, controllers: { registrations: "registrations" }

root to: "campaigns#home"
Expand All @@ -9,6 +10,7 @@
get 'debit_cards/new'
post 'debit_cards/create', to: 'debit_cards#create'
post 'debit_cards/destroy', to: 'debit_cards#destroy'
post 'disputes', to: 'disputes#create'
post 'instant_transfer', to: 'debit_cards#transfer'
get 'payouts/:id', to: 'payouts#show', as: 'payout'
post 'webhooks/stripe', to: 'webhooks#stripe'
Expand Down
5 changes: 5 additions & 0 deletions test/controllers/disputes_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'test_helper'

class DisputesControllerTest < ActionDispatch::IntegrationTest

end

0 comments on commit 1a5e5d0

Please sign in to comment.