spree-paypal_platform 5.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE +10 -0
- data/README.md +117 -0
- data/Rakefile +27 -0
- data/app/models/spree/payment_sessions/paypal_checkout.rb +94 -0
- data/app/models/spree/paypal_checkout/base.rb +14 -0
- data/app/models/spree/paypal_checkout/gateway/payment_sessions.rb +278 -0
- data/app/models/spree/paypal_checkout/gateway.rb +309 -0
- data/app/models/spree/paypal_checkout/order.rb +89 -0
- data/app/models/spree/paypal_checkout/order_decorator.rb +24 -0
- data/app/models/spree/paypal_checkout/payment_method_decorator.rb +29 -0
- data/app/models/spree/paypal_checkout/payment_sources/apple_pay.rb +49 -0
- data/app/models/spree/paypal_checkout/payment_sources/card.rb +42 -0
- data/app/models/spree/paypal_checkout/payment_sources/paypal.rb +37 -0
- data/app/models/spree/paypal_checkout/store_decorator.rb +19 -0
- data/app/presenters/spree/paypal_checkout/order_presenter.rb +130 -0
- data/app/services/spree/paypal_checkout/capture_order.rb +44 -0
- data/app/services/spree/paypal_checkout/create_payment.rb +49 -0
- data/app/services/spree/paypal_checkout/create_source.rb +109 -0
- data/app/views/spree/admin/payment_methods/configuration_guides/_spree_paypal_checkout.html.erb +16 -0
- data/app/views/spree/admin/payment_methods/descriptions/_spree_paypal_checkout.html.erb +10 -0
- data/app/views/spree/admin/payments/source_forms/_spree_paypal_checkout.html.erb +7 -0
- data/app/views/spree/payment_sources/_paypal_checkout.html.erb +17 -0
- data/config/initializers/spree.rb +8 -0
- data/db/migrate/20250528095719_create_spree_paypal_checkout_orders.rb +22 -0
- data/db/migrate/20260817120000_rewrite_spree_paypal_checkout_sti_types.rb +33 -0
- data/db/migrate/20260817130000_add_unique_index_on_paypal_checkout_order_paypal_id.rb +7 -0
- data/lib/generators/spree/paypal_checkout/install/install_generator.rb +39 -0
- data/lib/spree/paypal_checkout/engine.rb +41 -0
- data/lib/spree/paypal_checkout/factories.rb +59 -0
- data/lib/spree/paypal_checkout/version.rb +8 -0
- data/lib/spree/paypal_checkout.rb +45 -0
- data/lib/spree-paypal_checkout.rb +7 -0
- data/lib/spree-paypal_platform.rb +7 -0
- metadata +178 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
|
|
5
|
+
module Spree
|
|
6
|
+
module PaypalCheckout
|
|
7
|
+
##
|
|
8
|
+
# PayPal Checkout payment method.
|
|
9
|
+
#
|
|
10
|
+
# One method covers the PayPal wallet, Apple Pay, and Card Fields. Extra
|
|
11
|
+
# wallets are recorded as {PaymentSources} on the same gateway rather than
|
|
12
|
+
# as separate payment methods.
|
|
13
|
+
#
|
|
14
|
+
class Gateway < ::Spree::Gateway
|
|
15
|
+
include PaymentSessions
|
|
16
|
+
|
|
17
|
+
GatewayResponse = Struct.new(:success, :message, :params, :authorization) do
|
|
18
|
+
alias_method :success?, :success
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
preference :client_id, :string
|
|
22
|
+
preference :client_secret, :password
|
|
23
|
+
preference :webhook_secret, :string
|
|
24
|
+
preference :test_mode, :boolean, default: true
|
|
25
|
+
preference :enable_apple_pay, :boolean, default: true
|
|
26
|
+
preference :apple_pay_domain, :string
|
|
27
|
+
|
|
28
|
+
validates :preferred_client_id, :preferred_client_secret, presence: true
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# @return [Class]
|
|
32
|
+
#
|
|
33
|
+
def provider_class
|
|
34
|
+
self.class
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Default source class for admin "previous cards" lookups. Actual
|
|
39
|
+
# captured payments may also be {PaymentSources::ApplePay} or
|
|
40
|
+
# {PaymentSources::Card}.
|
|
41
|
+
#
|
|
42
|
+
# @return [Class]
|
|
43
|
+
#
|
|
44
|
+
def payment_source_class
|
|
45
|
+
PaymentSources::Paypal
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# @return [TrueClass]
|
|
50
|
+
#
|
|
51
|
+
def payment_profiles_supported?
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# @return [String]
|
|
57
|
+
#
|
|
58
|
+
def default_name
|
|
59
|
+
'PayPal'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# @return [String]
|
|
64
|
+
#
|
|
65
|
+
def method_type
|
|
66
|
+
'spree_paypal_checkout'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# @return [String]
|
|
71
|
+
#
|
|
72
|
+
def payment_icon_name
|
|
73
|
+
'paypal'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# @return [String]
|
|
78
|
+
#
|
|
79
|
+
def description_partial_name
|
|
80
|
+
'spree_paypal_checkout'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# @return [String]
|
|
85
|
+
#
|
|
86
|
+
def configuration_guide_partial_name
|
|
87
|
+
'spree_paypal_checkout'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# @return [String]
|
|
92
|
+
#
|
|
93
|
+
def source_partial_name
|
|
94
|
+
'paypal_checkout'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Whether the storefront should offer Apple Pay for this method.
|
|
99
|
+
#
|
|
100
|
+
# Domain registration with PayPal is still required; this flag only
|
|
101
|
+
# advertises the capability.
|
|
102
|
+
#
|
|
103
|
+
# @return [TrueClass, FalseClass]
|
|
104
|
+
#
|
|
105
|
+
def apple_pay_enabled?
|
|
106
|
+
preferred_enable_apple_pay
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# @return [String, NilClass]
|
|
111
|
+
#
|
|
112
|
+
def webhook_url
|
|
113
|
+
return nil unless store
|
|
114
|
+
|
|
115
|
+
"#{store.url_or_custom_domain}/api/v3/webhooks/payments/#{prefixed_id}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Persist a gateway-customer profile for a signed-in buyer.
|
|
120
|
+
#
|
|
121
|
+
# @param payment [Spree::Payment]
|
|
122
|
+
# @return [Spree::GatewayCustomer, NilClass]
|
|
123
|
+
#
|
|
124
|
+
def create_profile(payment)
|
|
125
|
+
user = payment.order.user
|
|
126
|
+
return if user.blank?
|
|
127
|
+
return if payment.source.blank?
|
|
128
|
+
return unless payment.source.is_a?(PaymentSources::Paypal)
|
|
129
|
+
|
|
130
|
+
paypal_account_id = payment.source.account_id
|
|
131
|
+
return if paypal_account_id.blank?
|
|
132
|
+
|
|
133
|
+
payment.payment_method.gateway_customers.find_or_create_by(user: user, profile_id: paypal_account_id)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
##
|
|
137
|
+
# @return [PaypalServerSdk::Client]
|
|
138
|
+
#
|
|
139
|
+
def client
|
|
140
|
+
@client ||= PaypalServerSdk::Client.new(
|
|
141
|
+
client_credentials_auth_credentials: PaypalServerSdk::ClientCredentialsAuthCredentials.new(
|
|
142
|
+
o_auth_client_id: preferred_client_id,
|
|
143
|
+
o_auth_client_secret: preferred_client_secret
|
|
144
|
+
),
|
|
145
|
+
environment: preferred_test_mode ? PaypalServerSdk::Environment::SANDBOX : PaypalServerSdk::Environment::PRODUCTION,
|
|
146
|
+
logging_configuration: PaypalServerSdk::LoggingConfiguration.new(
|
|
147
|
+
log_level: Logger::WARN,
|
|
148
|
+
request_logging_config: PaypalServerSdk::RequestLoggingConfiguration.new(
|
|
149
|
+
log_body: false
|
|
150
|
+
),
|
|
151
|
+
response_logging_config: PaypalServerSdk::ResponseLoggingConfiguration.new(
|
|
152
|
+
log_headers: false
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
# @raise [NotImplementedError] always — authorize-then-capture is unused
|
|
160
|
+
#
|
|
161
|
+
def authorize(_amount_in_cents, _payment_source, _gateway_options = {})
|
|
162
|
+
raise NotImplementedError, 'PayPal Checkout captures in one step; use #purchase'
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
##
|
|
166
|
+
# Purchase is authorize + capture in one step.
|
|
167
|
+
#
|
|
168
|
+
# @param amount_in_cents [Integer]
|
|
169
|
+
# @param payment_source [#paypal_id]
|
|
170
|
+
# @param gateway_options [Hash]
|
|
171
|
+
# @return [GatewayResponse]
|
|
172
|
+
#
|
|
173
|
+
def purchase(amount_in_cents, payment_source, gateway_options = {})
|
|
174
|
+
capture(amount_in_cents, payment_source.paypal_id, gateway_options)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Capture a previously created PayPal order.
|
|
179
|
+
#
|
|
180
|
+
# @param amount_in_cents [Integer]
|
|
181
|
+
# @param paypal_id [String] PayPal order ID
|
|
182
|
+
# @param gateway_options [Hash]
|
|
183
|
+
# @return [GatewayResponse]
|
|
184
|
+
#
|
|
185
|
+
def capture(_amount_in_cents, paypal_id, gateway_options = {})
|
|
186
|
+
protect_from_error do
|
|
187
|
+
order = find_order(gateway_options[:order_id])
|
|
188
|
+
return failure('Order not found') unless order
|
|
189
|
+
|
|
190
|
+
response = client.orders.capture_order({
|
|
191
|
+
'id' => paypal_id,
|
|
192
|
+
'prefer' => 'return=representation'
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if response.data.status == 'COMPLETED'
|
|
196
|
+
success(response.data.id, response.data.as_json)
|
|
197
|
+
else
|
|
198
|
+
failure('Failed to capture PayPal payment', response.data)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
##
|
|
204
|
+
# Void a PayPal authorization.
|
|
205
|
+
#
|
|
206
|
+
# @param authorization [String]
|
|
207
|
+
# @param _source [Object]
|
|
208
|
+
# @param gateway_options [Hash]
|
|
209
|
+
# @return [GatewayResponse]
|
|
210
|
+
#
|
|
211
|
+
def void(authorization, _source, _gateway_options = {})
|
|
212
|
+
protect_from_error do
|
|
213
|
+
response = client.payments.void_payment({
|
|
214
|
+
'authorization_id' => authorization,
|
|
215
|
+
'prefer' => 'return=representation'
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
success(authorization, response.data.as_json)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
##
|
|
223
|
+
# Refund a captured PayPal payment.
|
|
224
|
+
#
|
|
225
|
+
# @param amount_in_cents [Integer]
|
|
226
|
+
# @param _payment_source [Object]
|
|
227
|
+
# @param paypal_payment_id [String]
|
|
228
|
+
# @param gateway_options [Hash]
|
|
229
|
+
# @return [GatewayResponse]
|
|
230
|
+
#
|
|
231
|
+
def credit(amount_in_cents, _payment_source, paypal_payment_id, gateway_options = {})
|
|
232
|
+
refund_originator = gateway_options[:originator]
|
|
233
|
+
order = refund_originator.respond_to?(:order) ? refund_originator.order : refund_originator
|
|
234
|
+
|
|
235
|
+
return failure('Order not found') unless order
|
|
236
|
+
|
|
237
|
+
protect_from_error do
|
|
238
|
+
payload = {
|
|
239
|
+
capture_id: paypal_payment_id,
|
|
240
|
+
amount: {
|
|
241
|
+
value: (amount_in_cents / 100.0).to_s,
|
|
242
|
+
currency_code: order.currency.upcase
|
|
243
|
+
}
|
|
244
|
+
}.deep_stringify_keys
|
|
245
|
+
|
|
246
|
+
response = client.payments.refund_captured_payment(payload)
|
|
247
|
+
|
|
248
|
+
success(response.data.id, response.data.as_json)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
##
|
|
253
|
+
# Cancel a payment: refund if captured, otherwise void.
|
|
254
|
+
#
|
|
255
|
+
# @param authorization [String]
|
|
256
|
+
# @param payment [Spree::Payment, NilClass]
|
|
257
|
+
# @return [GatewayResponse]
|
|
258
|
+
#
|
|
259
|
+
def cancel(authorization, payment = nil)
|
|
260
|
+
protect_from_error do
|
|
261
|
+
if payment&.completed?
|
|
262
|
+
amount = payment.credit_allowed
|
|
263
|
+
return success(authorization, {}) if amount.zero?
|
|
264
|
+
|
|
265
|
+
refund = payment.refunds.create!(
|
|
266
|
+
amount: amount,
|
|
267
|
+
reason: Spree::RefundReason.order_canceled_reason,
|
|
268
|
+
refunder_id: payment.order.canceler_id
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
success(payment.response_code, refund.response.params)
|
|
272
|
+
else
|
|
273
|
+
response = client.payments.void_payment({
|
|
274
|
+
'authorization_id' => authorization,
|
|
275
|
+
'prefer' => 'return=representation'
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
success(authorization, response.data.as_json)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
def find_order(order_id)
|
|
286
|
+
return nil unless order_id
|
|
287
|
+
|
|
288
|
+
order_number, _payment_number = order_id.split('-')
|
|
289
|
+
Spree::Order.find_by(number: order_number)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def protect_from_error
|
|
293
|
+
yield
|
|
294
|
+
rescue PaypalServerSdk::APIException => e
|
|
295
|
+
raise Spree::Core::GatewayError, "PayPal API error: #{e.message}"
|
|
296
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, Errno::ECONNREFUSED => e
|
|
297
|
+
raise Spree::Core::GatewayError, "PayPal connection error: #{e.message}"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def success(authorization, response)
|
|
301
|
+
GatewayResponse.new(true, 'Transaction successful', response, authorization)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def failure(message, response = {})
|
|
305
|
+
GatewayResponse.new(false, message, response, nil)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
##
|
|
6
|
+
# A persisted PayPal Orders API response, used by the legacy
|
|
7
|
+
# `/api/v2/storefront/paypal_orders` flow.
|
|
8
|
+
#
|
|
9
|
+
# Payment-session hosts do not write this table; they keep the same
|
|
10
|
+
# payload on `Spree::PaymentSession#external_data`.
|
|
11
|
+
#
|
|
12
|
+
class Order < Base
|
|
13
|
+
class NotCapturedError < StandardError; end
|
|
14
|
+
class AlreadyCapturedError < StandardError; end
|
|
15
|
+
|
|
16
|
+
belongs_to :order, class_name: 'Spree::Order'
|
|
17
|
+
belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
|
|
18
|
+
alias gateway payment_method
|
|
19
|
+
|
|
20
|
+
before_validation :set_amount_from_order, on: :create
|
|
21
|
+
|
|
22
|
+
validates :paypal_id, presence: true, uniqueness: true
|
|
23
|
+
validates :data, presence: true
|
|
24
|
+
validates :amount, numericality: { greater_than: 0 }, presence: true
|
|
25
|
+
|
|
26
|
+
store_accessor :data, :payer, :purchase_units, :payment_source, :status
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Create a Spree::Payment for this captured PayPal order.
|
|
30
|
+
#
|
|
31
|
+
# @return [Spree::Payment]
|
|
32
|
+
# @raise [NotCapturedError] when the PayPal order is not COMPLETED
|
|
33
|
+
#
|
|
34
|
+
def create_payment!
|
|
35
|
+
raise NotCapturedError unless completed?
|
|
36
|
+
|
|
37
|
+
CreatePayment.new(
|
|
38
|
+
order: order,
|
|
39
|
+
paypal_order: self,
|
|
40
|
+
gateway: payment_method,
|
|
41
|
+
amount: amount
|
|
42
|
+
).call
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Capture the PayPal order via the Orders API.
|
|
47
|
+
#
|
|
48
|
+
# @return [Spree::PaypalCheckout::Order]
|
|
49
|
+
# @raise [AlreadyCapturedError] when already COMPLETED
|
|
50
|
+
#
|
|
51
|
+
def capture!
|
|
52
|
+
raise AlreadyCapturedError if completed?
|
|
53
|
+
|
|
54
|
+
CaptureOrder.new(paypal_order: self).call
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Capture ID from the stored payload. Only present after capture.
|
|
59
|
+
#
|
|
60
|
+
# @return [String, NilClass]
|
|
61
|
+
#
|
|
62
|
+
def paypal_payment_id
|
|
63
|
+
@paypal_payment_id ||= data.dig('purchase_units', 0, 'payments', 'captures', 0, 'id')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Fresh PayPal order from the API.
|
|
68
|
+
#
|
|
69
|
+
# @return [PaypalServerSdk::ApiResponse]
|
|
70
|
+
#
|
|
71
|
+
def paypal_order
|
|
72
|
+
@paypal_order ||= gateway.client.orders.get_order({ 'id' => paypal_id })
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# @return [TrueClass, FalseClass]
|
|
77
|
+
#
|
|
78
|
+
def completed?
|
|
79
|
+
status == 'COMPLETED'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def set_amount_from_order
|
|
85
|
+
self.amount ||= order&.total
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
##
|
|
6
|
+
# Adds the legacy PayPal-order association onto {Spree::Order}.
|
|
7
|
+
#
|
|
8
|
+
module OrderDecorator
|
|
9
|
+
##
|
|
10
|
+
# @param base [Class]
|
|
11
|
+
# @return [void]
|
|
12
|
+
#
|
|
13
|
+
def self.prepended(base)
|
|
14
|
+
base.store_accessor :private_metadata, :paypal_id
|
|
15
|
+
base.has_many :paypal_checkout_orders,
|
|
16
|
+
class_name: 'Spree::PaypalCheckout::Order',
|
|
17
|
+
dependent: :destroy,
|
|
18
|
+
foreign_key: :order_id
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Spree::Order.prepend(Spree::PaypalCheckout::OrderDecorator)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
##
|
|
6
|
+
# Adds a paypal_checkout scope and predicate onto {Spree::PaymentMethod}.
|
|
7
|
+
#
|
|
8
|
+
module PaymentMethodDecorator
|
|
9
|
+
##
|
|
10
|
+
# @param base [Class]
|
|
11
|
+
# @return [void]
|
|
12
|
+
#
|
|
13
|
+
def self.prepended(base)
|
|
14
|
+
base.scope :paypal_checkout, lambda {
|
|
15
|
+
where(type: Spree::PaypalCheckout.gateway_type_names)
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# @return [TrueClass, FalseClass]
|
|
21
|
+
#
|
|
22
|
+
def paypal_checkout?
|
|
23
|
+
Spree::PaypalCheckout.gateway_type_names.include?(type)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Spree::PaymentMethod.prepend(Spree::PaypalCheckout::PaymentMethodDecorator)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
module PaymentSources
|
|
6
|
+
##
|
|
7
|
+
# An Apple Pay wallet used through PayPal Checkout.
|
|
8
|
+
#
|
|
9
|
+
# PayPal returns `payment_source.apple_pay` on the captured order.
|
|
10
|
+
# The card brand / last digits (when present) are stored so admin
|
|
11
|
+
# can show "Apple Pay · Visa 4242" rather than a bare wallet name.
|
|
12
|
+
#
|
|
13
|
+
class ApplePay < ::Spree::PaymentSource
|
|
14
|
+
store_accessor :private_metadata,
|
|
15
|
+
:card_brand,
|
|
16
|
+
:last_digits,
|
|
17
|
+
:card_type,
|
|
18
|
+
:name,
|
|
19
|
+
:email
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# @return [Array<String>]
|
|
23
|
+
#
|
|
24
|
+
def actions
|
|
25
|
+
%w[credit void]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# @return [String]
|
|
30
|
+
#
|
|
31
|
+
def self.display_name
|
|
32
|
+
'Apple Pay'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Human-readable wallet line for admin and order emails.
|
|
37
|
+
#
|
|
38
|
+
# @return [String]
|
|
39
|
+
#
|
|
40
|
+
def display_payment_info
|
|
41
|
+
parts = ['Apple Pay']
|
|
42
|
+
parts << card_brand.to_s.titleize if card_brand.present?
|
|
43
|
+
parts << last_digits if last_digits.present?
|
|
44
|
+
parts.join(' · ')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
module PaymentSources
|
|
6
|
+
##
|
|
7
|
+
# An Advanced Credit and Debit Card (Card Fields) payment.
|
|
8
|
+
#
|
|
9
|
+
class Card < ::Spree::PaymentSource
|
|
10
|
+
store_accessor :private_metadata,
|
|
11
|
+
:card_brand,
|
|
12
|
+
:last_digits,
|
|
13
|
+
:card_type,
|
|
14
|
+
:name
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# @return [Array<String>]
|
|
18
|
+
#
|
|
19
|
+
def actions
|
|
20
|
+
%w[credit void]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# @return [String]
|
|
25
|
+
#
|
|
26
|
+
def self.display_name
|
|
27
|
+
'Card'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Human-readable card line for admin and order emails.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
#
|
|
35
|
+
def display_payment_info
|
|
36
|
+
parts = [card_brand.to_s.titleize.presence, last_digits].compact
|
|
37
|
+
parts.join(' · ').presence || 'Card'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
module PaymentSources
|
|
6
|
+
##
|
|
7
|
+
# A PayPal wallet account captured as a Spree payment source.
|
|
8
|
+
#
|
|
9
|
+
class Paypal < ::Spree::PaymentSource
|
|
10
|
+
store_accessor :private_metadata, :email, :name, :account_status, :account_id
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# @return [Array<String>]
|
|
14
|
+
#
|
|
15
|
+
def actions
|
|
16
|
+
%w[credit void]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# @return [String]
|
|
21
|
+
#
|
|
22
|
+
def self.display_name
|
|
23
|
+
'PayPal'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Human-readable wallet line for admin and order emails.
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
#
|
|
31
|
+
def display_payment_info
|
|
32
|
+
['PayPal', account_id].compact.join(' · ')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module PaypalCheckout
|
|
5
|
+
##
|
|
6
|
+
# Looks up the store's active PayPal Checkout gateway.
|
|
7
|
+
#
|
|
8
|
+
module StoreDecorator
|
|
9
|
+
##
|
|
10
|
+
# @return [Spree::PaypalCheckout::Gateway, NilClass]
|
|
11
|
+
#
|
|
12
|
+
def paypal_checkout_gateway
|
|
13
|
+
@paypal_checkout_gateway ||= payment_methods.paypal_checkout.active.last
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Spree::Store.prepend(Spree::PaypalCheckout::StoreDecorator)
|