spree_bitpay 1.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- metadata +44 -1243
- data/.gitignore +0 -26
- data/.rspec +0 -1
- data/.travis.yml +0 -18
- data/Gemfile +0 -7
- data/LICENSE.txt +0 -22
- data/README.md +0 -79
- data/Rakefile +0 -21
- data/app/assets/images/BC_nBG_64px.png +0 -0
- data/app/assets/images/bitcoin.png +0 -0
- data/app/assets/javascripts/easyModal.js-master/.gitignore +0 -1
- data/app/assets/javascripts/easyModal.js-master/README.md +0 -3
- data/app/assets/javascripts/easyModal.js-master/bower.json +0 -20
- data/app/assets/javascripts/easyModal.js-master/jquery.easyModal.js +0 -161
- data/app/assets/javascripts/spree/backend/spree_bitpay.js +0 -3
- data/app/assets/javascripts/spree/frontend/spree_bitpay.js +0 -86
- data/app/assets/stylesheets/spree/backend/spree_bitpay.css +0 -3
- data/app/assets/stylesheets/spree/frontend/spree_bitpay.css +0 -58
- data/app/controllers/spree/bitpay_controller.rb +0 -243
- data/app/models/order_decorator.rb +0 -17
- data/app/models/spree/bitpay_invoice.rb +0 -23
- data/app/models/spree/payment_method/bitpay.rb +0 -64
- data/app/overrides/spree/payments/_payment/bitpay_payment_params.html.erb.deface +0 -55
- data/app/views/spree/admin/payments/source_views/_bitpay.html.erb +0 -23
- data/app/views/spree/checkout/payment/_bitpay.html.erb +0 -4
- data/bin/rails +0 -7
- data/config/locales/en.yml +0 -16
- data/config/routes.rb +0 -9
- data/db/migrate/20140720052959_create_spree_bitpay_invoices.rb +0 -8
- data/db/migrate/20140725200946_add_fields_to_spree_bitpay_invoice.rb +0 -14
- data/db/migrate/20140729192827_add_index_to_spree_payments.rb +0 -5
- data/lib/generators/spree_bitpay/install/install_generator.rb +0 -32
- data/lib/spree_bitpay.rb +0 -3
- data/lib/spree_bitpay/engine.rb +0 -28
- data/lib/spree_bitpay/factories.rb +0 -6
- data/lib/spree_bitpay/version.rb +0 -3
- data/script/rails +0 -7
- data/spec/factories/bitpay_test_factories.rb +0 -58
- data/spec/features/bitpay_plugin_spec.rb +0 -75
- data/spec/fixtures/valid_confirmed_callback.json +0 -27
- data/spec/fixtures/valid_confirmed_invoice.json +0 -15
- data/spec/fixtures/valid_expired_invoice.json +0 -16
- data/spec/fixtures/valid_invalid_callback.json +0 -27
- data/spec/fixtures/valid_invalid_invoice.json +0 -15
- data/spec/fixtures/valid_new_invoice.json +0 -15
- data/spec/fixtures/valid_overpaid_callback.json +0 -27
- data/spec/fixtures/valid_overpaid_invoice.json +0 -15
- data/spec/fixtures/valid_paid_callback.json +0 -27
- data/spec/fixtures/valid_paid_invoice.json +0 -15
- data/spec/models/spree/order_spec.rb +0 -41
- data/spec/models/spree/payment_method/bitpay.rb +0 -26
- data/spec/requests/notifications_spec.rb +0 -218
- data/spec/spec_helper.rb +0 -131
- data/spree_bitpay.gemspec +0 -40
- data/testapp.sh +0 -6
@@ -1,58 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
*= require spree/frontend
|
3
|
-
*/
|
4
|
-
// Bitpay Payment Method styling
|
5
|
-
|
6
|
-
// No idea why this element is needed but it seems the first entry is ignored??
|
7
|
-
// TODO: Is this really needed?
|
8
|
-
|
9
|
-
#throwaway {
|
10
|
-
width: 20px;
|
11
|
-
}
|
12
|
-
|
13
|
-
#bitpay_modal_content {
|
14
|
-
padding:40px;
|
15
|
-
background-color: #FFF;
|
16
|
-
}
|
17
|
-
|
18
|
-
#bitpay_invoice_iframe {
|
19
|
-
width: 500px;
|
20
|
-
//height: 150px;
|
21
|
-
border: 1px solid #d9d9db;
|
22
|
-
overflow: hidden;
|
23
|
-
padding:20px;
|
24
|
-
max-width: 100%;
|
25
|
-
}
|
26
|
-
|
27
|
-
|
28
|
-
#bitpay_view_invoice_iframe {
|
29
|
-
width: 542px;
|
30
|
-
//height: 150px;
|
31
|
-
border: 1px solid #d9d9db;
|
32
|
-
overflow: hidden;
|
33
|
-
padding:20px;
|
34
|
-
max-width: 100%;
|
35
|
-
}
|
36
|
-
|
37
|
-
#bitpay_checkout_guidance {
|
38
|
-
text-align: center;
|
39
|
-
margin: auto;
|
40
|
-
}
|
41
|
-
|
42
|
-
#bitpay_payment_buttons {
|
43
|
-
padding-top: 10px;
|
44
|
-
text-align: center;
|
45
|
-
}
|
46
|
-
|
47
|
-
a.button.disabled {
|
48
|
-
background-color: #727276;
|
49
|
-
border-color: #59595c;
|
50
|
-
color: #CCC;
|
51
|
-
opacity: .65;
|
52
|
-
pointer-events: none;
|
53
|
-
}
|
54
|
-
|
55
|
-
.bitpay_invoice_details {
|
56
|
-
padding-top: 20px;
|
57
|
-
padding-bottom: 20px;
|
58
|
-
}
|
@@ -1,243 +0,0 @@
|
|
1
|
-
module Spree
|
2
|
-
class BitpayController < StoreController
|
3
|
-
skip_before_filter :verify_authenticity_token, :only => [:notification]
|
4
|
-
|
5
|
-
# Generates Bitpay Invoice and returns iframe view
|
6
|
-
#
|
7
|
-
def pay_now
|
8
|
-
|
9
|
-
order = current_order || raise(ActiveRecord::RecordNotFound)
|
10
|
-
|
11
|
-
return redirect_to root_url if order.state != "confirm"
|
12
|
-
|
13
|
-
# Find the payment by searching for valid payments associated with the order
|
14
|
-
# VOID payments are considered valid, so need to exclude those too
|
15
|
-
|
16
|
-
payment = order.get_bitpay_payment
|
17
|
-
|
18
|
-
logger.debug "Found payment: #{payment.inspect}"
|
19
|
-
|
20
|
-
case payment.state
|
21
|
-
when "checkout"
|
22
|
-
# New checkout - create an invoice, and attach its id to the payment.source
|
23
|
-
invoice = new_invoice(order, payment)
|
24
|
-
payment.source.invoice_id = invoice['id']
|
25
|
-
payment.source.save!
|
26
|
-
payment.started_processing!
|
27
|
-
else
|
28
|
-
# An invoice was already created - find it
|
29
|
-
invoice = payment.source.find_invoice
|
30
|
-
end
|
31
|
-
|
32
|
-
@invoice_iframe_url = "#{invoice['url']}&view=iframe"
|
33
|
-
render json: @invoice_iframe_url
|
34
|
-
end
|
35
|
-
|
36
|
-
# View Invoice with specific ID
|
37
|
-
#
|
38
|
-
def view_invoice
|
39
|
-
invoice = BitpayInvoice.find(params[:source_id]).find_invoice
|
40
|
-
redirect_to (invoice["url"] + '&view=iframe')
|
41
|
-
end
|
42
|
-
|
43
|
-
def check_payment_state
|
44
|
-
invoice = BitpayInvoice.where(invoice_id: params[:invoice_id]).first
|
45
|
-
pm = PaymentMethod.find(invoice.payment_method_id)
|
46
|
-
status = pm.scan_the_server(invoice.invoice_id)
|
47
|
-
render json: status
|
48
|
-
end
|
49
|
-
|
50
|
-
def cancel
|
51
|
-
|
52
|
-
order = current_order || raise(ActiveRecord::RecordNotFound)
|
53
|
-
|
54
|
-
# Find and invalidate Bitpay payment in processing state
|
55
|
-
order.payments.with_state("processing").each do |payment|
|
56
|
-
if (payment.payment_method.is_a? Spree::PaymentMethod::Bitpay)
|
57
|
-
payment.state = "invalid" # Have to set this explicitly since Spree state machine prevents it
|
58
|
-
payment.save!
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
redirect_to edit_order_url(order, state: 'payment'), :notice => Spree.t(:checkout_cancelled)
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
# Fires on receipt of payment received window message
|
67
|
-
#
|
68
|
-
def payment_sent
|
69
|
-
|
70
|
-
order = Spree::Order.find(session[:order_id]) || raise(ActiveRecord::RecordNotFound)
|
71
|
-
|
72
|
-
session[:order_id] = nil # Reset cart
|
73
|
-
redirect_to spree.order_path(order), :notice => Spree.t(:order_processed_successfully)
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
## Handle IPN from Bitpay server
|
78
|
-
# Receives incoming IPN message and retrieves official BitPay invoice for processing
|
79
|
-
#
|
80
|
-
def notification
|
81
|
-
|
82
|
-
posData = JSON.parse(params["posData"])
|
83
|
-
|
84
|
-
order_id = posData["orderID"]
|
85
|
-
payment_id = posData["paymentID"]
|
86
|
-
|
87
|
-
# Get OFFICIAL Invoice from BitPay API
|
88
|
-
# Fetching payment this way should prevent any false payment/order mismatch
|
89
|
-
order = Spree::Order.find_by_number(order_id) || raise(ActiveRecord::RecordNotFound)
|
90
|
-
payment = order.payments.find_by(identifier: payment_id) || raise(ActiveRecord::RecordNotFound)
|
91
|
-
invoice = payment.source.find_invoice
|
92
|
-
|
93
|
-
if invoice
|
94
|
-
logger.debug("Bitpay Invoice Content: " + invoice.to_json)
|
95
|
-
process_invoice(invoice)
|
96
|
-
head :ok
|
97
|
-
else
|
98
|
-
raise "Spree_Bitpay: No invoice found for notification for #{payment.identifier} from #{request.remote_ip}"
|
99
|
-
end
|
100
|
-
|
101
|
-
rescue
|
102
|
-
logger.error "Spree_Bitpay: Unprocessable notification received from #{request.remote_ip}: #{params.inspect}"
|
103
|
-
head :unprocessable_entity
|
104
|
-
end
|
105
|
-
|
106
|
-
# Reprocess Invoice and update order status
|
107
|
-
#
|
108
|
-
def refresh
|
109
|
-
payment = Spree::Payment.find(params[:payment]) # Retrieve payment by ID
|
110
|
-
old_state = payment.state
|
111
|
-
invoice = payment.source.find_invoice # Get associated invoice
|
112
|
-
process_invoice(invoice) # Re-process invoice
|
113
|
-
new_state = payment.reload.state
|
114
|
-
notice = (new_state == old_state) ? Spree.t(:bitpay_payment_not_updated) : (Spree.t(:bitpay_payment_updated) + new_state.titlecase)
|
115
|
-
redirect_to (request.referrer || root_path), notice: notice
|
116
|
-
end
|
117
|
-
|
118
|
-
#######################################################################
|
119
|
-
### Private Methods
|
120
|
-
#######################################################################
|
121
|
-
|
122
|
-
private
|
123
|
-
|
124
|
-
# Call Bitpay API and return new JSON invoice object
|
125
|
-
#
|
126
|
-
def new_invoice(order, payment)
|
127
|
-
|
128
|
-
# Have to encode this into a string for proper handling by API
|
129
|
-
posDataJson = {paymentID: payment.identifier, orderID: order.number}.to_json
|
130
|
-
|
131
|
-
invoice_params = {
|
132
|
-
price: order.outstanding_balance,
|
133
|
-
currency: order.currency,
|
134
|
-
orderID: order.number,
|
135
|
-
notificationURL: bitpay_notification_url,
|
136
|
-
posData: posDataJson,
|
137
|
-
fullNotifications: "true"
|
138
|
-
}
|
139
|
-
|
140
|
-
logger.debug "Requesting Invoice with params: #{invoice_params}"
|
141
|
-
invoice = payment.payment_method.get_bitpay_client.post 'invoice', invoice_params
|
142
|
-
logger.debug "Invoice Generated: #{invoice.inspect}"
|
143
|
-
|
144
|
-
return invoice
|
145
|
-
end
|
146
|
-
|
147
|
-
# Process the invoice and adjust order state accordingly
|
148
|
-
# Accepts BitPay JSON invoice object
|
149
|
-
#
|
150
|
-
def process_invoice(invoice)
|
151
|
-
logger.debug "Processing Bitpay invoice"
|
152
|
-
|
153
|
-
# Extract posData
|
154
|
-
posData = JSON.parse(invoice["posData"])
|
155
|
-
|
156
|
-
payment_id = posData["paymentID"]
|
157
|
-
status = invoice["status"]
|
158
|
-
exception_status = invoice["exceptionStatus"]
|
159
|
-
|
160
|
-
payment = Spree::Payment.find_by(identifier: payment_id) || raise(ActiveRecord::RecordNotFound)
|
161
|
-
|
162
|
-
logger.debug "Found Payment: #{payment.inspect}"
|
163
|
-
|
164
|
-
# Advance Payment state according to Spree flow
|
165
|
-
# http://guides.spreecommerce.com/user/payment_states.html
|
166
|
-
|
167
|
-
case status
|
168
|
-
when "new"
|
169
|
-
|
170
|
-
payment.started_processing
|
171
|
-
|
172
|
-
when "paid"
|
173
|
-
|
174
|
-
# Move payment to pending state and complete order
|
175
|
-
|
176
|
-
if payment.state = "processing" # This is the most common scenario
|
177
|
-
payment.pend!
|
178
|
-
else # In the case it was previously marked invalid due to invoice expiry
|
179
|
-
payment.state = "pending"
|
180
|
-
payment.save
|
181
|
-
end
|
182
|
-
|
183
|
-
payment.order.update!
|
184
|
-
payment.order.next
|
185
|
-
if (!payment.order.complete?)
|
186
|
-
raise "Can't transition order #{payment.order.number} to COMPLETE state"
|
187
|
-
end
|
188
|
-
|
189
|
-
when "confirmed", "complete"
|
190
|
-
|
191
|
-
# Move payment to 'complete' state
|
192
|
-
|
193
|
-
case payment.state
|
194
|
-
when "pending" # This is the most common scenario
|
195
|
-
payment.complete
|
196
|
-
when "completed"
|
197
|
-
# Do nothing
|
198
|
-
else
|
199
|
-
# Something unusual happened - maybe a notification was missed, or
|
200
|
-
# Make sure the order is completed
|
201
|
-
if !payment.order.complete?
|
202
|
-
payment.state = "pending"
|
203
|
-
payment.save
|
204
|
-
payment.order.next
|
205
|
-
if (!payment.order.complete?)
|
206
|
-
raise "Can't transition order #{payment.order.number} to COMPLETE state"
|
207
|
-
end
|
208
|
-
end
|
209
|
-
payment.state = "completed" # Can't use Spree payment.complete! method since we are transitioning from weird states
|
210
|
-
payment.save
|
211
|
-
end
|
212
|
-
|
213
|
-
when "expired"
|
214
|
-
|
215
|
-
if (exception_status == false) # This is an abandoned invoice
|
216
|
-
payment.state = "invalid" # Have to set this explicitly since Spree state machine prevents it
|
217
|
-
payment.save!
|
218
|
-
else
|
219
|
-
# Don't think this will be anything other than paidPartial exceptionStatus
|
220
|
-
unless payment.state == 'void'
|
221
|
-
payment.void!
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
when "invalid"
|
226
|
-
|
227
|
-
unless payment.state == 'failed'
|
228
|
-
payment.failure! # Will be flagged risky automatically
|
229
|
-
end
|
230
|
-
|
231
|
-
else
|
232
|
-
|
233
|
-
raise "Unexpected status received from BitPay: '#{invoice["status"]}' for '#{invoice["url"]}"
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
|
-
logger.debug "New Payment State for #{payment.identifier}: #{payment.state}"
|
238
|
-
logger.debug "New Order State for #{payment.order.number}: #{payment.order.state}"
|
239
|
-
|
240
|
-
end
|
241
|
-
|
242
|
-
end
|
243
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
Spree::Order.class_eval do
|
2
|
-
self.state_machine.before_transition :to => :confirm, :do => :validate_bitpay_payment
|
3
|
-
|
4
|
-
def validate_bitpay_payment
|
5
|
-
states = payments.map(&:state)
|
6
|
-
payments.each do |payment|
|
7
|
-
payment.failure if payment.state == 'processing'
|
8
|
-
end if (states.include?('checkout') && states.include?('processing'))
|
9
|
-
end
|
10
|
-
|
11
|
-
def get_bitpay_payment
|
12
|
-
checkout = payments.select{|payment| payment.state == 'checkout'}
|
13
|
-
processing = payments.select{|payment| payment.state == 'processing'}
|
14
|
-
return checkout.last if checkout.any?
|
15
|
-
return processing.last if processing.any?
|
16
|
-
end
|
17
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Spree
|
2
|
-
|
3
|
-
class BitpayInvoice < ActiveRecord::Base
|
4
|
-
belongs_to :payment_method
|
5
|
-
belongs_to :user
|
6
|
-
has_many :payments, as: :source
|
7
|
-
|
8
|
-
# DB fields: user_id, invoice_id
|
9
|
-
|
10
|
-
attr_accessor :bogus # bogus since we need to have a param that is passed to trigger Payment.build_source
|
11
|
-
|
12
|
-
def actions
|
13
|
-
# TODO: Refund action?
|
14
|
-
["void"]
|
15
|
-
end
|
16
|
-
|
17
|
-
# Gets the JSON invoice from Bitpay
|
18
|
-
def find_invoice
|
19
|
-
payment_method.find_invoice(invoice_id)
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
module Spree
|
2
|
-
class PaymentMethod::Bitpay < PaymentMethod
|
3
|
-
preference :api_key, :string
|
4
|
-
preference :api_endpoint, :string, :default => "https://bitpay.com/api"
|
5
|
-
|
6
|
-
def auto_capture?
|
7
|
-
true
|
8
|
-
end
|
9
|
-
|
10
|
-
def payment_source_class
|
11
|
-
Spree::BitpayInvoice
|
12
|
-
end
|
13
|
-
|
14
|
-
# Set true to force confirmation step.
|
15
|
-
# http://guides.spreecommerce.com/developer/checkout.html#confirmation
|
16
|
-
#
|
17
|
-
def payment_profiles_supported?
|
18
|
-
true
|
19
|
-
end
|
20
|
-
|
21
|
-
## Dummy method to satisfy test factories
|
22
|
-
#
|
23
|
-
def create_profile(payment)
|
24
|
-
nil
|
25
|
-
end
|
26
|
-
|
27
|
-
def source_required?
|
28
|
-
false
|
29
|
-
end
|
30
|
-
|
31
|
-
#######################################################################
|
32
|
-
### Instance Utility Methods
|
33
|
-
#######################################################################
|
34
|
-
|
35
|
-
## Retreive Invoice by ID from BitPay
|
36
|
-
#
|
37
|
-
def find_invoice(id)
|
38
|
-
id ? ( get_bitpay_client.get ('invoice/' + id) ) : nil
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
def scan_the_server(id)
|
43
|
-
message = get_bitpay_client.get('invoice/' + id)
|
44
|
-
status = message["status"]
|
45
|
-
return status unless status.nil?
|
46
|
-
error = message["error"]["message"] if status.nil?
|
47
|
-
return error unless error.nil?
|
48
|
-
"unknown error"
|
49
|
-
end
|
50
|
-
|
51
|
-
## Interface with BitPay
|
52
|
-
#
|
53
|
-
def get_bitpay_client
|
54
|
-
BitPay::Client.new(preferred_api_key, {api_uri: preferred_api_endpoint})
|
55
|
-
end
|
56
|
-
|
57
|
-
## This is a stub method which simply returns true to allow the cancel on the Spree side
|
58
|
-
#
|
59
|
-
def void (action, order_id, id)
|
60
|
-
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success')
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
<!-- insert_after 'erb[loud]:contains("payment_method")' -->
|
2
|
-
|
3
|
-
<% if (payment.payment_method.is_a? Spree::PaymentMethod::Bitpay) && (payment.state != 'void') %>
|
4
|
-
<% pm = payment.payment_method %>
|
5
|
-
<%= image_tag("BC_nBG_64px.png") %>
|
6
|
-
<script type="text/javascript">
|
7
|
-
$(document).ready(function() {
|
8
|
-
$('[data-hook="buttons"] input[name="commit"]').click(Bitpay.checkout);
|
9
|
-
$('#checkout_form_confirm').removeAttr("action");
|
10
|
-
$('#checkout_form_confirm').removeAttr("method");
|
11
|
-
|
12
|
-
$('#bitpay_checkout_modal').easyModal({
|
13
|
-
overlayClose: false,
|
14
|
-
closeOnEscape: false
|
15
|
-
});
|
16
|
-
|
17
|
-
Bitpay.invoiceUrl = "<%= j bitpay_pay_now_url(pmid: pm.id).html_safe %>";
|
18
|
-
Bitpay.checkUrl = "<%= j bitpay_check_url %>";
|
19
|
-
Bitpay.apiEndpoint = "<%= pm.get_preference(:api_endpoint) %>";
|
20
|
-
})
|
21
|
-
|
22
|
-
window.addEventListener('message', Bitpay.finishCheckout, false);
|
23
|
-
|
24
|
-
</script>
|
25
|
-
|
26
|
-
<div id="bitpay_checkout_modal">
|
27
|
-
<div id="bitpay_modal_content">
|
28
|
-
<div id="bitpay_invoice">
|
29
|
-
<iframe id="bitpay_invoice_iframe"
|
30
|
-
scrolling="no"
|
31
|
-
allowtransparency="true"
|
32
|
-
frameborder="0", >
|
33
|
-
</iframe>
|
34
|
-
</div>
|
35
|
-
<div id="bitpay_checkout_guidance">
|
36
|
-
<span id="instructions"><%= simple_format Spree.t(:bitpay_payment_instructions) %></span>
|
37
|
-
<span id="completed" style="display:none"><%= Spree.t(:bitpay_payment_completed) %></span>
|
38
|
-
<span id="expired" style="display:none"><%= Spree.t(:bitpay_invoice_expired) %></span>
|
39
|
-
</br>
|
40
|
-
</div>
|
41
|
-
<div id="bitpay_payment_buttons" class="form-buttons">
|
42
|
-
<a id="continue_to_invoice"
|
43
|
-
class="button disabled"
|
44
|
-
disabled="disabled"
|
45
|
-
href="<%= bitpay_payment_sent_url %>">
|
46
|
-
<span><%= Spree.t(:bitpay_view_invoice) %></span>
|
47
|
-
</a>
|
48
|
-
<%= link_to Spree.t(:bitpay_cancel), bitpay_cancel_url, {id: "choose_another_method", class: "button primary"} %>
|
49
|
-
</div>
|
50
|
-
</div>
|
51
|
-
</div>
|
52
|
-
|
53
|
-
<% end %>
|
54
|
-
|
55
|
-
|