pay 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pay might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +99 -31
- data/Rakefile +19 -19
- data/app/controllers/pay/payments_controller.rb +7 -0
- data/app/controllers/pay/webhooks/braintree_controller.rb +19 -6
- data/app/mailers/pay/application_mailer.rb +2 -2
- data/app/mailers/pay/user_mailer.rb +12 -0
- data/app/models/pay/subscription.rb +41 -10
- data/app/views/layouts/pay/application.html.erb +11 -5
- data/app/views/pay/payments/show.html.erb +134 -0
- data/app/views/pay/user_mailer/payment_action_required.html.erb +6 -0
- data/config/locales/en.yml +17 -0
- data/config/routes.rb +3 -2
- data/db/migrate/20190816015720_add_status_to_subscriptions.rb +14 -0
- data/lib/generators/pay/email_views_generator.rb +3 -3
- data/lib/generators/pay/views_generator.rb +13 -0
- data/lib/pay/billable/sync_email.rb +3 -4
- data/lib/pay/billable.rb +25 -18
- data/lib/pay/braintree/billable.rb +88 -82
- data/lib/pay/braintree/charge.rb +0 -2
- data/lib/pay/braintree/subscription.rb +74 -72
- data/lib/pay/braintree.rb +6 -6
- data/lib/pay/engine.rb +10 -10
- data/lib/pay/env.rb +0 -1
- data/lib/pay/payment.rb +52 -0
- data/lib/pay/receipts.rb +7 -7
- data/lib/pay/stripe/billable.rb +68 -37
- data/lib/pay/stripe/charge.rb +0 -2
- data/lib/pay/stripe/subscription.rb +7 -6
- data/lib/pay/stripe/webhooks/charge_refunded.rb +0 -2
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +9 -10
- data/lib/pay/stripe/webhooks/customer_deleted.rb +5 -7
- data/lib/pay/stripe/webhooks/customer_updated.rb +0 -2
- data/lib/pay/stripe/webhooks/payment_action_required.rb +27 -0
- data/lib/pay/stripe/webhooks/{source_deleted.rb → payment_method_updated.rb} +1 -3
- data/lib/pay/stripe/webhooks/subscription_created.rb +6 -11
- data/lib/pay/stripe/webhooks/subscription_deleted.rb +1 -3
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +0 -2
- data/lib/pay/stripe/webhooks/subscription_updated.rb +13 -10
- data/lib/pay/stripe/webhooks.rb +17 -11
- data/lib/pay/stripe.rb +5 -5
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +42 -15
- metadata +27 -39
@@ -0,0 +1,134 @@
|
|
1
|
+
<div id="app" class="h-full md:flex md:justify-center md:items-center">
|
2
|
+
<div class="w-full max-w-lg">
|
3
|
+
<!-- Status Messages -->
|
4
|
+
<p class="flex items-center mb-4 bg-red-100 border border-red-200 px-5 py-2 rounded-lg text-red-500" v-if="errorMessage">
|
5
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="flex-shrink-0 w-6 h-6">
|
6
|
+
<path class="fill-current text-red-300" d="M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20z"/>
|
7
|
+
<path class="fill-current text-red-500" d="M12 18a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm1-5.9c-.13 1.2-1.88 1.2-2 0l-.5-5a1 1 0 0 1 1-1.1h1a1 1 0 0 1 1 1.1l-.5 5z"/>
|
8
|
+
</svg>
|
9
|
+
|
10
|
+
<span class="ml-3">{{ errorMessage }}</span>
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<p class="flex items-center mb-4 bg-green-100 border border-green-200 px-5 py-4 rounded-lg text-green-700" v-if="paymentProcessed && successMessage">
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="flex-shrink-0 w-6 h-6">
|
15
|
+
<circle cx="12" cy="12" r="10" class="fill-current text-green-300"/>
|
16
|
+
<path class="fill-current text-green-500" d="M10 14.59l6.3-6.3a1 1 0 0 1 1.4 1.42l-7 7a1 1 0 0 1-1.4 0l-3-3a1 1 0 0 1 1.4-1.42l2.3 2.3z"/>
|
17
|
+
</svg>
|
18
|
+
|
19
|
+
<span class="ml-3">{{ successMessage }}</span>
|
20
|
+
</p>
|
21
|
+
|
22
|
+
<div class="bg-white rounded-lg shadow-xl p-4 sm:py-6 sm:px-10 mb-5">
|
23
|
+
<% if @payment.succeeded? %>
|
24
|
+
<h1 class="text-xl mt-2 mb-4 text-gray-700"><%=t "successful.header" %></h1>
|
25
|
+
<p class="mb-6"><%=t "successful.description" %></p>
|
26
|
+
|
27
|
+
<% elsif @payment.canceled? %>
|
28
|
+
<h1 class="text-xl mt-2 mb-4 text-gray-700"><%=t "cancelled.header" %></h1>
|
29
|
+
<p class="mb-6"><%=t "cancelled.description" %></p>
|
30
|
+
|
31
|
+
<% else %>
|
32
|
+
<div id="payment-elements" v-if="! paymentProcessed">
|
33
|
+
<!-- Instructions -->
|
34
|
+
<h1 class="text-xl mt-2 mb-4 text-gray-700"><%=t "requires_action.header", amount: number_to_currency(@payment.amount / 100.0) %></h1>
|
35
|
+
<p class="mb-6"><%=t "requires_action.description" %></p>
|
36
|
+
|
37
|
+
<div v-show="status == 'requires_payment_method'">
|
38
|
+
<!-- Name -->
|
39
|
+
<label for="cardholder-name" class="inline-block text-sm text-gray-700 font-semibold mb-2"><%=t "requires_action.full_name" %></label>
|
40
|
+
<input id="cardholder-name" type="text" placeholder="Jane Doe" required class="inline-block text-black bg-gray-200 border border-gray-400 rounded-lg w-full px-4 py-3 mb-3 focus:outline-none" v-model="name">
|
41
|
+
|
42
|
+
<!-- Card -->
|
43
|
+
<label for="card-element" class="inline-block text-sm text-gray-700 font-semibold mb-2"><%=t "requires_action.card" %></label>
|
44
|
+
<div id="card-element" class="bg-gray-200 border border-gray-400 rounded-lg p-4 mb-6"></div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<!-- Pay Button -->
|
48
|
+
<button id="card-button" class="inline-block w-full px-4 py-3 mb-4 text-white rounded-lg bg-blue-400 hover:bg-blue-500" :class="{ 'bg-blue-400': paymentProcessing, 'bg-blue-600': ! paymentProcessing }" @click="confirmPayment" :disabled="paymentProcessing">
|
49
|
+
<%=t "requires_action.button", amount: number_to_currency(@payment.amount / 100.0) %>
|
50
|
+
</button>
|
51
|
+
</div>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
<%= link_to t("back"), root_path, class: "inline-block w-full px-4 py-3 bg-gray-200 hover:bg-gray-300 text-center text-gray-700 rounded-lg" %>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<p class="text-center text-gray-500 text-sm">
|
58
|
+
© <%= Date.current.year %> <%= Pay.business_name %> <%=t "all_rights_reserved" %>
|
59
|
+
</p>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
|
63
|
+
<script>
|
64
|
+
window.stripe = Stripe('<%= Pay::Stripe.public_key %>');
|
65
|
+
var app = new Vue({
|
66
|
+
el: '#app',
|
67
|
+
data: {
|
68
|
+
clientSecret: '<%= @payment.client_secret %>',
|
69
|
+
status: '<%= @payment.status %>',
|
70
|
+
name: '',
|
71
|
+
cardElement: null,
|
72
|
+
paymentProcessing: false,
|
73
|
+
paymentProcessed: false,
|
74
|
+
successMessage: '',
|
75
|
+
errorMessage: ''
|
76
|
+
},
|
77
|
+
|
78
|
+
mounted: function () {
|
79
|
+
if (this.status == "succeeded" || this.status == "canceled") {
|
80
|
+
return
|
81
|
+
}
|
82
|
+
|
83
|
+
// We can trigger SCA immediately if this payment requires action
|
84
|
+
// This makes sure a new SCA subscription doesn't have to put in their card twice
|
85
|
+
if (this.status == "requires_action") {
|
86
|
+
this.paymentProcessing = true
|
87
|
+
this.paymentProcessed = false
|
88
|
+
stripe.confirmCardPayment(this.clientSecret).then(this.handleConfirmResult.bind(this))
|
89
|
+
}
|
90
|
+
|
91
|
+
// Setup elements in case the authentication fails and user needs to put in a new card
|
92
|
+
const elements = stripe.elements();
|
93
|
+
this.cardElement = elements.create('card');
|
94
|
+
this.cardElement.mount('#card-element');
|
95
|
+
},
|
96
|
+
|
97
|
+
methods: {
|
98
|
+
confirmPayment: function () {
|
99
|
+
this.paymentProcessing = true
|
100
|
+
this.paymentProcessed = false
|
101
|
+
this.successMessage = ''
|
102
|
+
this.errorMessage = ''
|
103
|
+
|
104
|
+
stripe.confirmCardPayment(
|
105
|
+
this.clientSecret,
|
106
|
+
{
|
107
|
+
payment_method: {
|
108
|
+
card: this.cardElement,
|
109
|
+
billing_details: { name: this.name }
|
110
|
+
},
|
111
|
+
save_payment_method: true,
|
112
|
+
setup_future_usage: 'off_session',
|
113
|
+
}
|
114
|
+
).then(this.handleConfirmResult.bind(this))
|
115
|
+
},
|
116
|
+
|
117
|
+
handleConfirmResult(result) {
|
118
|
+
this.paymentProcessing = false;
|
119
|
+
if (result.error) {
|
120
|
+
if (result.error.code === 'parameter_invalid_empty' &&
|
121
|
+
result.error.param === 'payment_method_data[billing_details][name]') {
|
122
|
+
this.errorMessage = '<%=t "requires_action.name_missing" %>'
|
123
|
+
} else {
|
124
|
+
this.errorMessage = result.error.message
|
125
|
+
this.status = result.error.payment_intent.status
|
126
|
+
}
|
127
|
+
} else {
|
128
|
+
this.paymentProcessed = true;
|
129
|
+
this.successMessage = '<%=t "requires_action.success" %>'
|
130
|
+
}
|
131
|
+
}
|
132
|
+
},
|
133
|
+
})
|
134
|
+
</script>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<h3>Extra confirmation is needed to process your payment</h3>
|
2
|
+
<p>Your <%= Pay.business_name %> subscription requires confirmation to process your payment to continue access.</p>
|
3
|
+
|
4
|
+
<p>You may confirm your payment via your account. If you have any questions, please hit reply and let us know.</p>
|
5
|
+
|
6
|
+
<p>- The <%= Pay.business_name %> Team</p>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
en:
|
2
|
+
successful:
|
3
|
+
header: Payment Successful
|
4
|
+
description: This payment was already successfully confirmed.
|
5
|
+
cancelled:
|
6
|
+
header: Payment Cancelled
|
7
|
+
description: This payment was cancelled.
|
8
|
+
requires_action:
|
9
|
+
header: Confirm your %{amount} payment
|
10
|
+
description: Extra confirmation is needed to process your payment. Please confirm your payment by filling out your payment details below.
|
11
|
+
full_name: Full name
|
12
|
+
card: Card
|
13
|
+
button: Pay %{amount}
|
14
|
+
name_missing: Please provide your name.
|
15
|
+
success: The payment was successful.
|
16
|
+
all_rights_reserved: All rights reserved.
|
17
|
+
back: Go back
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Pay::Engine.routes.draw do
|
4
|
-
|
5
|
-
post
|
4
|
+
resources :payments, only: [:show], module: :pay
|
5
|
+
post "webhooks/stripe", to: "stripe_event/webhook#event"
|
6
|
+
post "webhooks/braintree", to: "pay/webhooks/braintree#create"
|
6
7
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class AddStatusToSubscriptions < ActiveRecord::Migration[5.2]
|
2
|
+
def self.up
|
3
|
+
add_column :pay_subscriptions, :status, :string
|
4
|
+
|
5
|
+
# Any existing subscriptions should be marked as 'active'
|
6
|
+
# This won't actually make them active if their ends_at column is expired
|
7
|
+
Pay::Subscription.reset_column_information
|
8
|
+
Pay::Subscription.update_all(status: :active)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
remove_column :pay_subscriptions, :status
|
13
|
+
end
|
14
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "rails/generators"
|
2
2
|
|
3
3
|
module Pay
|
4
4
|
module Generators
|
@@ -6,8 +6,8 @@ module Pay
|
|
6
6
|
source_root File.expand_path("../../../..", __FILE__)
|
7
7
|
|
8
8
|
def copy_views
|
9
|
-
directory
|
9
|
+
directory "app/views/pay/user_mailer", "app/views/pay/user_mailer"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
-
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Generators
|
5
|
+
class ViewsGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../../../..", __FILE__)
|
7
|
+
|
8
|
+
def copy_views
|
9
|
+
directory "app/views/pay", "app/views/pay"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -15,12 +15,11 @@ module Pay
|
|
15
15
|
extend ActiveSupport::Concern
|
16
16
|
|
17
17
|
included do
|
18
|
-
after_update :enqeue_sync_email_job,
|
19
|
-
if: :should_sync_email_with_processor?
|
18
|
+
after_update :enqeue_sync_email_job, if: :should_sync_email_with_processor?
|
20
19
|
end
|
21
20
|
|
22
21
|
def should_sync_email_with_processor?
|
23
|
-
|
22
|
+
try(:saved_change_to_email?)
|
24
23
|
end
|
25
24
|
|
26
25
|
def sync_email_with_processor
|
@@ -32,7 +31,7 @@ module Pay
|
|
32
31
|
def enqeue_sync_email_job
|
33
32
|
# Only update if the processor id is the same
|
34
33
|
# This prevents duplicate API hits if this is their first time
|
35
|
-
if processor_id? && !
|
34
|
+
if processor_id? && !saved_change_to_processor_id? && saved_change_to_email?
|
36
35
|
EmailSyncJob.perform_later(id)
|
37
36
|
end
|
38
37
|
end
|
data/lib/pay/billable.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "pay/billable/sync_email"
|
2
2
|
|
3
3
|
module Pay
|
4
4
|
module Billable
|
@@ -38,7 +38,7 @@ module Pay
|
|
38
38
|
send("create_#{processor}_charge", amount_in_cents, options)
|
39
39
|
end
|
40
40
|
|
41
|
-
def subscribe(name:
|
41
|
+
def subscribe(name: "default", plan: "default", **options)
|
42
42
|
check_for_processor
|
43
43
|
send("create_#{processor}_subscription", name, plan, options)
|
44
44
|
end
|
@@ -49,25 +49,25 @@ module Pay
|
|
49
49
|
send("update_#{processor}_card", token)
|
50
50
|
end
|
51
51
|
|
52
|
-
def on_trial?(name:
|
52
|
+
def on_trial?(name: "default", plan: nil)
|
53
53
|
return true if default_generic_trial?(name, plan)
|
54
54
|
|
55
55
|
sub = subscription(name: name)
|
56
|
-
return sub
|
56
|
+
return sub&.on_trial? if plan.nil?
|
57
57
|
|
58
|
-
sub
|
58
|
+
sub&.on_trial? && sub.processor_plan == plan
|
59
59
|
end
|
60
60
|
|
61
61
|
def on_generic_trial?
|
62
62
|
trial_ends_at? && trial_ends_at > Time.zone.now
|
63
63
|
end
|
64
64
|
|
65
|
-
def processor_subscription(subscription_id)
|
65
|
+
def processor_subscription(subscription_id, options = {})
|
66
66
|
check_for_processor
|
67
|
-
send("#{processor}_subscription", subscription_id)
|
67
|
+
send("#{processor}_subscription", subscription_id, options)
|
68
68
|
end
|
69
69
|
|
70
|
-
def subscribed?(name:
|
70
|
+
def subscribed?(name: "default", processor_plan: nil)
|
71
71
|
subscription = subscription(name: name)
|
72
72
|
|
73
73
|
return false if subscription.nil?
|
@@ -76,17 +76,17 @@ module Pay
|
|
76
76
|
subscription.active? && subscription.processor_plan == processor_plan
|
77
77
|
end
|
78
78
|
|
79
|
-
def on_trial_or_subscribed?(name:
|
79
|
+
def on_trial_or_subscribed?(name: "default", processor_plan: nil)
|
80
80
|
on_trial?(name: name, plan: processor_plan) ||
|
81
81
|
subscribed?(name: name, processor_plan: processor_plan)
|
82
82
|
end
|
83
83
|
|
84
|
-
def subscription(name:
|
84
|
+
def subscription(name: "default")
|
85
85
|
subscriptions.for_name(name).last
|
86
86
|
end
|
87
87
|
|
88
|
-
def invoice!
|
89
|
-
send("#{processor}_invoice!")
|
88
|
+
def invoice!(options = {})
|
89
|
+
send("#{processor}_invoice!", options)
|
90
90
|
end
|
91
91
|
|
92
92
|
def upcoming_invoice
|
@@ -105,27 +105,34 @@ module Pay
|
|
105
105
|
braintree? && card_type == "PayPal"
|
106
106
|
end
|
107
107
|
|
108
|
+
def has_incomplete_payment?(name: "default")
|
109
|
+
subscription(name: name)&.has_incomplete_payment?
|
110
|
+
end
|
111
|
+
|
108
112
|
private
|
109
113
|
|
110
114
|
def check_for_processor
|
111
115
|
raise StandardError, "No payment processor selected. Make sure to set the #{Pay.billable_class}'s `processor` attribute to either 'stripe' or 'braintree'." unless processor
|
112
116
|
end
|
113
117
|
|
114
|
-
|
115
|
-
|
116
|
-
|
118
|
+
# Used for creating a Pay::Subscription in the database
|
119
|
+
def create_subscription(subscription, processor, name, plan, options = {})
|
120
|
+
options[:quantity] ||= 1
|
121
|
+
|
122
|
+
options.merge!(
|
123
|
+
name: name || "default",
|
117
124
|
processor: processor,
|
118
125
|
processor_id: subscription.id,
|
119
126
|
processor_plan: plan,
|
120
127
|
trial_ends_at: send("#{processor}_trial_end_date", subscription),
|
121
|
-
|
122
|
-
ends_at: nil
|
128
|
+
ends_at: nil,
|
123
129
|
)
|
130
|
+
subscriptions.create!(options)
|
124
131
|
end
|
125
132
|
|
126
133
|
def default_generic_trial?(name, plan)
|
127
134
|
# Generic trials don't have plans or custom names
|
128
|
-
plan.nil? && name ==
|
135
|
+
plan.nil? && name == "default" && on_generic_trial?
|
129
136
|
end
|
130
137
|
end
|
131
138
|
end
|
@@ -16,7 +16,7 @@ module Pay
|
|
16
16
|
)
|
17
17
|
raise Pay::Error.new(result.message) unless result.success?
|
18
18
|
|
19
|
-
update(processor:
|
19
|
+
update(processor: "braintree", processor_id: result.customer.id)
|
20
20
|
|
21
21
|
if card_token.present?
|
22
22
|
update_braintree_card_on_file result.customer.payment_methods.last
|
@@ -31,11 +31,11 @@ module Pay
|
|
31
31
|
# Handles Billable#charge
|
32
32
|
#
|
33
33
|
# Returns a Pay::Charge
|
34
|
-
def create_braintree_charge(amount, options={})
|
34
|
+
def create_braintree_charge(amount, options = {})
|
35
35
|
args = {
|
36
36
|
amount: amount / 100.0,
|
37
37
|
customer_id: customer.id,
|
38
|
-
options: {
|
38
|
+
options: {submit_for_settlement: true},
|
39
39
|
}.merge(options)
|
40
40
|
|
41
41
|
result = gateway.transaction.sale(args)
|
@@ -47,10 +47,15 @@ module Pay
|
|
47
47
|
# Handles Billable#subscribe
|
48
48
|
#
|
49
49
|
# Returns Pay::Subscription
|
50
|
-
def create_braintree_subscription(name, plan, options={})
|
50
|
+
def create_braintree_subscription(name, plan, options = {})
|
51
51
|
token = customer.payment_methods.find(&:default?).try(:token)
|
52
52
|
raise Pay::Error, "Customer has no default payment method" if token.nil?
|
53
53
|
|
54
|
+
# Standardize the trial period options
|
55
|
+
if (trial_period_days = options.delete(:trial_period_days)) && trial_period_days > 0
|
56
|
+
options.merge!(trial_period: true, trial_duration: trial_period_days, trial_duration_unit: :day)
|
57
|
+
end
|
58
|
+
|
54
59
|
subscription_options = options.merge(
|
55
60
|
payment_method_token: token,
|
56
61
|
plan_id: plan
|
@@ -59,7 +64,7 @@ module Pay
|
|
59
64
|
result = gateway.subscription.create(subscription_options)
|
60
65
|
raise Pay::Error.new(result.message) unless result.success?
|
61
66
|
|
62
|
-
create_subscription(result.subscription,
|
67
|
+
create_subscription(result.subscription, "braintree", name, plan, status: :active)
|
63
68
|
rescue ::Braintree::BraintreeError => e
|
64
69
|
raise Error, e.message
|
65
70
|
end
|
@@ -73,7 +78,7 @@ module Pay
|
|
73
78
|
payment_method_nonce: token,
|
74
79
|
options: {
|
75
80
|
make_default: true,
|
76
|
-
verify_card: true
|
81
|
+
verify_card: true,
|
77
82
|
}
|
78
83
|
)
|
79
84
|
raise Pay::Error.new(result.message) unless result.success?
|
@@ -87,30 +92,31 @@ module Pay
|
|
87
92
|
|
88
93
|
def update_braintree_email!
|
89
94
|
braintree_customer.update(
|
90
|
-
email:
|
95
|
+
email: email,
|
91
96
|
first_name: try(:first_name),
|
92
|
-
last_name:
|
97
|
+
last_name: try(:last_name),
|
93
98
|
)
|
94
99
|
end
|
95
100
|
|
96
101
|
def braintree_trial_end_date(subscription)
|
97
102
|
return unless subscription.trial_period
|
98
|
-
|
103
|
+
# Braintree returns dates without time zones, so we'll assume they're UTC
|
104
|
+
Time.parse(subscription.first_billing_date).end_of_day
|
99
105
|
end
|
100
106
|
|
101
107
|
def update_subscriptions_to_payment_method(token)
|
102
108
|
subscriptions.each do |subscription|
|
103
109
|
if subscription.active?
|
104
|
-
gateway.subscription.update(subscription.processor_id, {
|
110
|
+
gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
|
105
111
|
end
|
106
112
|
end
|
107
113
|
end
|
108
114
|
|
109
|
-
def braintree_subscription(subscription_id)
|
115
|
+
def braintree_subscription(subscription_id, options = {})
|
110
116
|
gateway.subscription.find(subscription_id)
|
111
117
|
end
|
112
118
|
|
113
|
-
def braintree_invoice!
|
119
|
+
def braintree_invoice!(options = {})
|
114
120
|
# pass
|
115
121
|
end
|
116
122
|
|
@@ -120,7 +126,7 @@ module Pay
|
|
120
126
|
|
121
127
|
def save_braintree_transaction(transaction)
|
122
128
|
attrs = card_details_for_braintree_transaction(transaction)
|
123
|
-
attrs
|
129
|
+
attrs[:amount] = transaction.amount.to_f * 100
|
124
130
|
|
125
131
|
charge = charges.find_or_initialize_by(
|
126
132
|
processor: :braintree,
|
@@ -132,80 +138,80 @@ module Pay
|
|
132
138
|
|
133
139
|
private
|
134
140
|
|
135
|
-
|
136
|
-
|
137
|
-
|
141
|
+
def gateway
|
142
|
+
Pay.braintree_gateway
|
143
|
+
end
|
138
144
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
when ::Braintree::PayPalAccount
|
150
|
-
update!(
|
151
|
-
card_type: "PayPal",
|
152
|
-
card_last4: payment_method.email
|
153
|
-
)
|
154
|
-
end
|
145
|
+
def update_braintree_card_on_file(payment_method)
|
146
|
+
case payment_method
|
147
|
+
when ::Braintree::CreditCard
|
148
|
+
update!(
|
149
|
+
card_type: payment_method.card_type,
|
150
|
+
card_last4: payment_method.last_4,
|
151
|
+
card_exp_month: payment_method.expiration_month,
|
152
|
+
card_exp_year: payment_method.expiration_year
|
153
|
+
)
|
155
154
|
|
156
|
-
|
157
|
-
|
155
|
+
when ::Braintree::PayPalAccount
|
156
|
+
update!(
|
157
|
+
card_type: "PayPal",
|
158
|
+
card_last4: payment_method.email
|
159
|
+
)
|
158
160
|
end
|
159
161
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
162
|
+
# Clear the card token so we don't accidentally update twice
|
163
|
+
self.card_token = nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def card_details_for_braintree_transaction(transaction)
|
167
|
+
case transaction.payment_instrument_type
|
168
|
+
when "credit_card", "samsung_pay_card", "masterpass_card", "samsung_pay_card", "visa_checkout_card"
|
169
|
+
payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
|
170
|
+
{
|
171
|
+
card_type: payment_method.card_type,
|
172
|
+
card_last4: payment_method.last_4,
|
173
|
+
card_exp_month: payment_method.expiration_month,
|
174
|
+
card_exp_year: payment_method.expiration_year,
|
175
|
+
}
|
176
|
+
|
177
|
+
when "paypal_account"
|
178
|
+
{
|
179
|
+
card_type: "PayPal",
|
180
|
+
card_last4: transaction.paypal_details.payer_email,
|
181
|
+
card_exp_month: nil,
|
182
|
+
card_exp_year: nil,
|
183
|
+
}
|
184
|
+
|
185
|
+
when "android_pay_card"
|
186
|
+
payment_method = transaction.android_pay_details
|
187
|
+
{
|
188
|
+
card_type: payment_method.source_card_type,
|
189
|
+
card_last4: payment_method.source_card_last_4,
|
190
|
+
card_exp_month: payment_method.expiration_month,
|
191
|
+
card_exp_year: payment_method.expiration_year,
|
192
|
+
}
|
193
|
+
|
194
|
+
when "venmo_account"
|
195
|
+
{
|
196
|
+
card_type: "Venmo",
|
197
|
+
card_last4: transaction.venmo_account_details.username,
|
198
|
+
card_exp_month: nil,
|
199
|
+
card_exp_year: nil,
|
200
|
+
}
|
201
|
+
|
202
|
+
when "apple_pay_card"
|
203
|
+
payment_method = transaction.apple_pay_details
|
204
|
+
{
|
205
|
+
card_type: payment_method.card_type,
|
206
|
+
card_last4: payment_method.last_4,
|
207
|
+
card_exp_month: payment_method.expiration_month,
|
208
|
+
card_exp_year: payment_method.expiration_year,
|
209
|
+
}
|
210
|
+
|
211
|
+
else
|
212
|
+
{}
|
208
213
|
end
|
214
|
+
end
|
209
215
|
end
|
210
216
|
end
|
211
217
|
end
|