spree_paypal_checkout 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e93096fd5f88baa021c6d077b03ec743036f7193f00410cee544c3c2662ab559
4
- data.tar.gz: 354f5e005d1aabcab8709de1bef7f4298986bf3dbcbbb6089d096ede2f77bde3
3
+ metadata.gz: bfb11878efce5e64446b6e83c9f16a69b64203ff47ce9ca601f1404b6f0ed017
4
+ data.tar.gz: 86705e3ed5b1c0314c2518c793edc022d5149e64a32d710919455b0e3ef4fe42
5
5
  SHA512:
6
- metadata.gz: 7df72e3c2201e90ae60a61ab37c30b84d00b074743ef63be0f6eb90ac60b454ff2dac778fb20a44730a56d5464ee42b6660e22cbb146ad85d3b3f7937d706776
7
- data.tar.gz: a9552304abcfdf6ffd8899514c81efab45c90506cf6541f3004e96c4c212bffa08e08d3ed2118791ee3dff01c85894456f644e22a170773bcab9faf79cc6e714
6
+ metadata.gz: fb11160ee57ed3ab5275c2e32c8d13463b74149002ed2297cd95045f53dc3101c8a1f97fff3b8424c4d0ae6e5ba5003c623ad2d12d4226183dbb30aa8c5a4c95
7
+ data.tar.gz: 74a7e4a0cdd3f55e976bb948ee8bc8a9799b55ec076319c353720342ffb0b80e087f0229e1046e0fc6f4b74dae88ff019ad1231f6a8f127ddbdfff1cc73dba23
@@ -0,0 +1,76 @@
1
+ module Spree
2
+ class PaymentSessions::PaypalCheckout < PaymentSession
3
+ def paypal_order_id
4
+ external_id
5
+ end
6
+
7
+ def paypal_capture_id
8
+ external_data&.dig('purchase_units', 0, 'payments', 'captures', 0, 'id')
9
+ end
10
+
11
+ def paypal_payer
12
+ external_data&.dig('payer')
13
+ end
14
+
15
+ def paypal_payment_source
16
+ external_data&.dig('payment_source')
17
+ end
18
+
19
+ def accepted?
20
+ external_data&.dig('status') == 'COMPLETED'
21
+ end
22
+
23
+ def successful?
24
+ accepted?
25
+ end
26
+
27
+ # Creates or finds the Spree::Payment for this session.
28
+ # Defers creation until paypal_capture_id is present so response_code
29
+ # is always the capture ID (required for refunds via Gateway#credit).
30
+ def find_or_create_payment!(metadata = {})
31
+ return unless persisted?
32
+ return payment if payment.present?
33
+ return unless paypal_capture_id
34
+
35
+ order.with_lock do
36
+ existing_payment = order.payments.where(
37
+ payment_method: payment_method,
38
+ response_code: paypal_capture_id
39
+ ).first
40
+
41
+ return existing_payment if existing_payment.present?
42
+
43
+ source = create_payment_source!
44
+
45
+ order.payments.create!(
46
+ payment_method: payment_method,
47
+ amount: amount,
48
+ response_code: paypal_capture_id,
49
+ source: source,
50
+ skip_source_requirement: true,
51
+ private_metadata: metadata
52
+ )
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def create_payment_source!
59
+ paypal_data = paypal_payment_source&.dig('paypal')
60
+ return nil unless paypal_data
61
+
62
+ source = SpreePaypalCheckout::PaymentSources::Paypal.find_or_initialize_by(
63
+ payment_method: payment_method,
64
+ gateway_payment_profile_id: paypal_data['account_id']
65
+ )
66
+ source.update!(
67
+ user: order.user,
68
+ email: paypal_data['email_address'],
69
+ name: "#{paypal_data.dig('name', 'given_name')} #{paypal_data.dig('name', 'surname')}".strip,
70
+ account_id: paypal_data['account_id'],
71
+ account_status: paypal_data['account_status']
72
+ )
73
+ source
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ class PaymentSetupSessions::PaypalCheckout < PaymentSetupSession
3
+ # PayPal's Vault API uses setup tokens (temporary) that get exchanged for
4
+ # permanent payment tokens once the buyer approves on PayPal.
5
+ def paypal_setup_token_id
6
+ external_id
7
+ end
8
+
9
+ def paypal_payment_token_id
10
+ external_data&.dig('payment_token', 'id')
11
+ end
12
+
13
+ # The approve link the storefront redirects the buyer to.
14
+ def approve_link
15
+ Array(external_data&.dig('links')).find { |l| l['rel'] == 'approve' }&.dig('href')
16
+ end
17
+
18
+ def successful?
19
+ status == 'completed'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,204 @@
1
+ require 'net/http'
2
+
3
+ module SpreePaypalCheckout
4
+ class Gateway < ::Spree::Gateway
5
+ module PaymentSessions
6
+ extend ActiveSupport::Concern
7
+
8
+ def session_required?
9
+ !SpreePaypalCheckout::Config[:use_legacy_api]
10
+ end
11
+
12
+ def payment_session_class
13
+ Spree::PaymentSessions::PaypalCheckout
14
+ end
15
+
16
+ # Creates a PayPal order via the PayPal Orders API and persists
17
+ # a Spree::PaymentSessions::PaypalCheckout record.
18
+ def create_payment_session(order:, amount: nil, external_data: {})
19
+ total = amount.presence || order.total_minus_store_credits
20
+
21
+ return nil if total.zero?
22
+
23
+ protect_from_error do
24
+ order_presenter = SpreePaypalCheckout::OrderPresenter.new(order)
25
+ paypal_response = client.orders.create_order(order_presenter.to_json)
26
+
27
+ payment_session_class.create!(
28
+ order: order,
29
+ payment_method: self,
30
+ amount: total,
31
+ currency: order.currency,
32
+ status: 'pending',
33
+ external_id: paypal_response.data.id,
34
+ customer: order.user,
35
+ external_data: paypal_response.data.as_json
36
+ )
37
+ end
38
+ end
39
+
40
+ def update_payment_session(payment_session:, amount: nil, external_data: {})
41
+ attrs = {}
42
+ attrs[:amount] = amount if amount.present?
43
+
44
+ if external_data.present?
45
+ attrs[:external_data] = (payment_session.external_data || {}).merge(external_data.stringify_keys)
46
+ end
47
+
48
+ payment_session.update!(attrs) if attrs.any?
49
+ end
50
+
51
+ # Completes a payment session by capturing the PayPal order,
52
+ # creating the Payment record, and transitioning the session.
53
+ #
54
+ # Does NOT complete the order -- that is handled by Carts::Complete
55
+ # (called by the storefront or by the webhook handler).
56
+ def complete_payment_session(payment_session:, params: {})
57
+ paypal_order_id = payment_session.external_id
58
+
59
+ response = client.orders.capture_order({
60
+ 'id' => paypal_order_id,
61
+ 'prefer' => 'return=representation'
62
+ })
63
+
64
+ # Persist capture data outside the lock so it survives if post-capture bookkeeping fails
65
+ payment_session.update!(external_data: response.data.as_json)
66
+
67
+ payment_session.order.with_lock do
68
+ if response.data.status == 'COMPLETED'
69
+ payment_session.process if payment_session.can_process?
70
+
71
+ payment = payment_session.find_or_create_payment!
72
+
73
+ if payment.present? && !payment.completed?
74
+ payment.started_processing! if payment.checkout?
75
+ payment.complete! if payment.can_complete?
76
+ end
77
+
78
+ create_profile(payment) if payment&.source.present?
79
+
80
+ payment_session.complete unless payment_session.completed?
81
+ else
82
+ payment_session.fail if payment_session.can_fail?
83
+ end
84
+ end
85
+ rescue PaypalServerSdk::APIException => e
86
+ payment_session.fail if payment_session.can_fail?
87
+ raise Spree::Core::GatewayError, "PayPal API error: #{e.message}"
88
+ end
89
+
90
+ # Parses incoming PayPal webhook events.
91
+ def parse_webhook_event(raw_body, headers)
92
+ verify_webhook_signature!(raw_body, headers)
93
+
94
+ event = JSON.parse(raw_body).with_indifferent_access
95
+ event_type = event[:event_type]
96
+ resource = event[:resource] || {}
97
+
98
+ paypal_order_id = extract_order_id_from_webhook(event_type, resource)
99
+ return nil unless paypal_order_id
100
+
101
+ payment_session = Spree::PaymentSessions::PaypalCheckout.find_by(
102
+ payment_method: self,
103
+ external_id: paypal_order_id
104
+ )
105
+ return nil unless payment_session
106
+
107
+ case event_type
108
+ when 'CHECKOUT.ORDER.APPROVED'
109
+ { action: :authorized, payment_session: payment_session, metadata: { paypal_event: event } }
110
+ when 'PAYMENT.CAPTURE.COMPLETED'
111
+ { action: :captured, payment_session: payment_session, metadata: { paypal_event: event } }
112
+ when 'PAYMENT.CAPTURE.DENIED', 'PAYMENT.CAPTURE.DECLINED'
113
+ { action: :failed, payment_session: payment_session, metadata: { paypal_event: event } }
114
+ when 'PAYMENT.CAPTURE.REVERSED', 'PAYMENT.CAPTURE.REFUNDED'
115
+ { action: :canceled, payment_session: payment_session, metadata: { paypal_event: event } }
116
+ else
117
+ nil
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def extract_order_id_from_webhook(event_type, resource)
124
+ case event_type
125
+ when /\ACHECKOUT\.ORDER\./
126
+ resource['id']
127
+ when /\APAYMENT\.CAPTURE\./
128
+ resource.dig('supplementary_data', 'related_ids', 'order_id')
129
+ end
130
+ end
131
+
132
+ def verify_webhook_signature!(raw_body, headers)
133
+ if preferred_webhook_secret.blank?
134
+ return if Rails.env.development? || Rails.env.test?
135
+
136
+ raise Spree::PaymentMethod::WebhookSignatureError,
137
+ 'PayPal webhook_secret is not configured'
138
+ end
139
+
140
+ transmission_id = headers['HTTP_PAYPAL_TRANSMISSION_ID'] || headers['PAYPAL-TRANSMISSION-ID']
141
+ transmission_time = headers['HTTP_PAYPAL_TRANSMISSION_TIME'] || headers['PAYPAL-TRANSMISSION-TIME']
142
+ cert_url = headers['HTTP_PAYPAL_CERT_URL'] || headers['PAYPAL-CERT-URL']
143
+ auth_algo = headers['HTTP_PAYPAL_AUTH_ALGO'] || headers['PAYPAL-AUTH-ALGO']
144
+ transmission_sig = headers['HTTP_PAYPAL_TRANSMISSION_SIG'] || headers['PAYPAL-TRANSMISSION-SIG']
145
+
146
+ unless transmission_id && transmission_sig
147
+ raise Spree::PaymentMethod::WebhookSignatureError, 'Missing PayPal webhook headers'
148
+ end
149
+
150
+ token = obtain_access_token
151
+
152
+ api_base = preferred_test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'
153
+ uri = URI("#{api_base}/v1/notifications/verify-webhook-signature")
154
+
155
+ payload = {
156
+ auth_algo: auth_algo,
157
+ cert_url: cert_url,
158
+ transmission_id: transmission_id,
159
+ transmission_sig: transmission_sig,
160
+ transmission_time: transmission_time,
161
+ webhook_id: preferred_webhook_secret,
162
+ webhook_event: JSON.parse(raw_body)
163
+ }
164
+
165
+ http = Net::HTTP.new(uri.host, uri.port)
166
+ http.use_ssl = true
167
+ http.open_timeout = 5
168
+ http.read_timeout = 10
169
+ request = Net::HTTP::Post.new(uri.path, {
170
+ 'Content-Type' => 'application/json',
171
+ 'Authorization' => "Bearer #{token}"
172
+ })
173
+ request.body = payload.to_json
174
+
175
+ response = http.request(request)
176
+ result = JSON.parse(response.body)
177
+
178
+ unless result['verification_status'] == 'SUCCESS'
179
+ raise Spree::PaymentMethod::WebhookSignatureError, 'Invalid webhook signature'
180
+ end
181
+ rescue Spree::PaymentMethod::WebhookSignatureError
182
+ raise
183
+ rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, JSON::ParserError, Errno::ECONNREFUSED => e
184
+ raise Spree::PaymentMethod::WebhookSignatureError, "Webhook verification failed: #{e.message}"
185
+ end
186
+
187
+ def obtain_access_token
188
+ api_base = preferred_test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'
189
+ uri = URI("#{api_base}/v1/oauth2/token")
190
+
191
+ http = Net::HTTP.new(uri.host, uri.port)
192
+ http.use_ssl = true
193
+ http.open_timeout = 5
194
+ http.read_timeout = 10
195
+ request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
196
+ request.basic_auth(preferred_client_id, preferred_client_secret)
197
+ request.body = 'grant_type=client_credentials'
198
+
199
+ response = http.request(request)
200
+ JSON.parse(response.body)['access_token']
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,116 @@
1
+ module SpreePaypalCheckout
2
+ class Gateway < ::Spree::Gateway
3
+ module PaymentSetupSessions
4
+ extend ActiveSupport::Concern
5
+
6
+ def setup_session_supported?
7
+ true
8
+ end
9
+
10
+ def payment_setup_session_class
11
+ Spree::PaymentSetupSessions::PaypalCheckout
12
+ end
13
+
14
+ # Creates a PayPal Vault setup token (temporary, awaiting buyer approval)
15
+ # and persists a Spree::PaymentSetupSessions::PaypalCheckout record.
16
+ #
17
+ # @param customer [Spree::User] the customer to vault the PayPal account for
18
+ # @param external_data [Hash] optional :return_url and :cancel_url for the
19
+ # PayPal redirect flow
20
+ # @return [Spree::PaymentSetupSessions::PaypalCheckout]
21
+ def create_payment_setup_session(customer:, external_data: {})
22
+ return_url = external_data[:return_url] || external_data['return_url'] || default_setup_return_url
23
+ cancel_url = external_data[:cancel_url] || external_data['cancel_url'] || default_setup_cancel_url
24
+
25
+ protect_from_error do
26
+ response = client.vault.create_setup_token(
27
+ SpreePaypalCheckout::SetupTokenPresenter.new(
28
+ customer: customer,
29
+ return_url: return_url,
30
+ cancel_url: cancel_url
31
+ ).to_h
32
+ )
33
+
34
+ payment_setup_session_class.create!(
35
+ customer: customer,
36
+ payment_method: self,
37
+ status: 'pending',
38
+ external_id: response.data.id,
39
+ external_data: response.data.as_json.merge(
40
+ 'return_url' => return_url,
41
+ 'cancel_url' => cancel_url
42
+ )
43
+ )
44
+ end
45
+ end
46
+
47
+ # Completes a setup session by exchanging the approved setup token for a
48
+ # permanent payment token, creates a Spree::PaymentSource representing the
49
+ # vaulted PayPal account, and transitions the session.
50
+ #
51
+ # @param setup_session [Spree::PaymentSetupSessions::PaypalCheckout]
52
+ # @param params [Hash] unused — PayPal needs no params beyond the setup_session
53
+ def complete_payment_setup_session(setup_session:, params: {})
54
+ protect_from_error do
55
+ response = client.vault.create_payment_token(
56
+ 'body' => {
57
+ 'payment_source' => {
58
+ 'token' => {
59
+ 'id' => setup_session.external_id,
60
+ 'type' => 'SETUP_TOKEN'
61
+ }
62
+ }
63
+ }
64
+ )
65
+
66
+ payment_token = response.data
67
+
68
+ setup_session.payment_source = build_payment_source_from_token(setup_session, payment_token)
69
+ setup_session.update!(
70
+ external_data: setup_session.external_data.to_h.merge('payment_token' => payment_token.as_json)
71
+ )
72
+ setup_session.complete if setup_session.can_complete?
73
+ rescue PaypalServerSdk::APIException => e
74
+ setup_session.fail if setup_session.can_fail?
75
+ raise Spree::Core::GatewayError, "PayPal Vault error: #{e.message}"
76
+ end
77
+
78
+ setup_session
79
+ end
80
+
81
+ private
82
+
83
+ def build_payment_source_from_token(setup_session, payment_token)
84
+ paypal = payment_token.as_json.dig('payment_source', 'paypal') || {}
85
+ account_id = paypal['email_address'] || payment_token.id
86
+
87
+ source = SpreePaypalCheckout::PaymentSources::Paypal.find_or_initialize_by(
88
+ payment_method: self,
89
+ gateway_payment_profile_id: payment_token.id
90
+ )
91
+ source.update!(
92
+ user: setup_session.customer,
93
+ email: paypal['email_address'],
94
+ name: [paypal.dig('name', 'given_name'), paypal.dig('name', 'surname')].compact.join(' ').strip.presence,
95
+ account_id: account_id,
96
+ account_status: paypal['account_status']
97
+ )
98
+ source
99
+ end
100
+
101
+ def default_setup_return_url
102
+ store = stores.first
103
+ return nil unless store
104
+
105
+ "#{store.storefront_url}/paypal/payment_setup_sessions/return"
106
+ end
107
+
108
+ def default_setup_cancel_url
109
+ store = stores.first
110
+ return nil unless store
111
+
112
+ "#{store.storefront_url}/paypal/payment_setup_sessions/cancel"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,6 +1,7 @@
1
1
  module SpreePaypalCheckout
2
2
  class Gateway < ::Spree::Gateway
3
3
  include PaypalServerSdk
4
+ include PaymentSessions
4
5
 
5
6
  GatewayResponse = Struct.new(:success, :message, :params, :authorization) do
6
7
  alias_method :success?, :success
@@ -11,6 +12,7 @@ module SpreePaypalCheckout
11
12
  #
12
13
  preference :client_id, :password
13
14
  preference :client_secret, :password
15
+ preference :webhook_secret, :string
14
16
  preference :test_mode, :boolean, default: true
15
17
 
16
18
  #
@@ -54,6 +56,13 @@ module SpreePaypalCheckout
54
56
  'paypal_checkout'
55
57
  end
56
58
 
59
+ def webhook_url
60
+ store = stores.first
61
+ return nil unless store
62
+
63
+ "#{store.formatted_url}/api/v3/webhooks/payments/#{prefixed_id}"
64
+ end
65
+
57
66
  def create_profile(payment)
58
67
  user = payment.order.user
59
68
  return if user.blank?
@@ -1,7 +1,7 @@
1
1
  module SpreePaypalCheckout
2
2
  module PaymentSources
3
3
  class Paypal < ::Spree::PaymentSource
4
- store_accessor :public_metadata, :email, :name, :account_status, :account_id
4
+ store_accessor :private_metadata, :email, :name, :account_status, :account_id
5
5
 
6
6
  def actions
7
7
  %w[credit void]
@@ -0,0 +1,37 @@
1
+ module SpreePaypalCheckout
2
+ # Builds the body of a PayPal Vault /v3/vault/setup-tokens request for tokenizing
3
+ # a buyer's PayPal account without an immediate purchase.
4
+ class SetupTokenPresenter
5
+ def initialize(customer:, return_url:, cancel_url:)
6
+ @customer = customer
7
+ @return_url = return_url
8
+ @cancel_url = cancel_url
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ 'body' => {
14
+ 'payment_source' => {
15
+ 'paypal' => {
16
+ 'usage_type' => 'MERCHANT',
17
+ 'experience_context' => {
18
+ 'return_url' => return_url,
19
+ 'cancel_url' => cancel_url
20
+ }
21
+ }
22
+ }
23
+ }.merge(customer_payload)
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :customer, :return_url, :cancel_url
30
+
31
+ def customer_payload
32
+ return {} unless customer&.id
33
+
34
+ { 'customer' => { 'id' => "customer_#{customer.id}" } }
35
+ end
36
+ end
37
+ end
@@ -37,10 +37,10 @@ module SpreePaypalCheckout
37
37
  def create_paypal_source
38
38
  source = SpreePaypalCheckout::PaymentSources::Paypal.find_or_create_by!(
39
39
  payment_method: gateway,
40
- user: user,
41
40
  gateway_payment_profile_id: paypal_payment_source['paypal']['account_id']
42
41
  )
43
42
  source.update!(
43
+ user: user,
44
44
  email: paypal_payment_source['paypal']['email_address'],
45
45
  name: "#{paypal_payment_source['paypal']['name']['given_name']} #{paypal_payment_source['paypal']['name']['surname']}".strip,
46
46
  account_id: paypal_payment_source['paypal']['account_id'],
@@ -1,6 +1,7 @@
1
1
  <div class="alert alert-info">
2
2
  <p class="mb-0">
3
3
  To find your <strong>Client ID</strong> and <strong>Client Secret</strong>, go to the
4
- <%= external_link_to 'PayPal dashboard', 'https://www.paypal.com/mep/merchantapps/setup/checkout/apicredentials', class: 'alert-link' %>
4
+ <%= external_link_to 'PayPal Developer Dashboard', 'https://developer.paypal.com/dashboard/', class: 'alert-link' %>.
5
+ For more information, see the <%= external_link_to 'PayPal REST API documentation', 'https://developer.paypal.com/api/rest/', class: 'alert-link' %>.
5
6
  </p>
6
7
  </div>
data/config/routes.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  Spree::Core::Engine.add_routes do
2
- namespace :api, defaults: { format: 'json' } do
3
- namespace :v2 do
4
- namespace :storefront do
5
- resources :paypal_orders, only: [:create] do
6
- member do
7
- put :capture
2
+ # Storefront API v2 (only when spree_legacy_api_v2 gem is available)
3
+ if defined?(SpreeLegacyApiV2::Engine)
4
+ namespace :api, defaults: { format: 'json' } do
5
+ namespace :v2 do
6
+ namespace :storefront do
7
+ resources :paypal_orders, only: [:create] do
8
+ member do
9
+ put :capture
10
+ end
8
11
  end
9
12
  end
10
13
  end
@@ -1,13 +1,5 @@
1
1
  module SpreePaypalCheckout
2
2
  class Configuration < Spree::Preferences::Configuration
3
-
4
- # Some example preferences are shown below, for more information visit:
5
- # https://docs.spreecommerce.org/developer/contributing/creating-an-extension
6
-
7
- # preference :enabled, :boolean, default: true
8
- # preference :dark_chocolate, :boolean, default: true
9
- # preference :color, :string, default: 'Red'
10
- # preference :favorite_number, :integer
11
- # preference :supported_locales, :array, default: [:en]
3
+ preference :use_legacy_api, :boolean, default: false
12
4
  end
13
5
  end
@@ -4,6 +4,12 @@ module SpreePaypalCheckout
4
4
  isolate_namespace Spree
5
5
  engine_name 'spree_paypal_checkout'
6
6
 
7
+ # Only load API v2 controllers and serializers when spree_legacy_api_v2 gem is available
8
+ if defined?(SpreeLegacyApiV2::Engine)
9
+ config.autoload_paths << root.join('lib', 'spree_api_v2')
10
+ config.eager_load_paths << root.join('lib', 'spree_api_v2')
11
+ end
12
+
7
13
  # use rspec for tests
8
14
  config.generators do |g|
9
15
  g.test_framework :rspec
@@ -20,6 +20,21 @@ FactoryBot.define do
20
20
  gateway_customer_profile_id { 'PAY-CUSTOMER-ID' }
21
21
  end
22
22
 
23
+ factory :paypal_checkout_payment_session, class: 'Spree::PaymentSessions::PaypalCheckout' do
24
+ association :order, factory: :order
25
+ association :payment_method, factory: :paypal_checkout_gateway
26
+ amount { order.total }
27
+ currency { order.currency }
28
+ status { 'pending' }
29
+ external_id { "PAYPAL-ORDER-#{SecureRandom.hex(8).upcase}" }
30
+ external_data { JSON.parse(File.read(SpreePaypalCheckout::Engine.root.join('spec', 'fixtures', 'paypal_order.json'))) }
31
+
32
+ factory :completed_paypal_checkout_payment_session do
33
+ status { 'completed' }
34
+ external_data { JSON.parse(File.read(SpreePaypalCheckout::Engine.root.join('spec', 'fixtures', 'captured_paypal_order.json'))) }
35
+ end
36
+ end
37
+
23
38
  factory :paypal_checkout_order, class: 'SpreePaypalCheckout::Order' do
24
39
  paypal_id { 'PAY-ORDER-ID' }
25
40
  order { create(:order) }
@@ -1,5 +1,5 @@
1
1
  module SpreePaypalCheckout
2
- VERSION = '0.6.0'.freeze
2
+ VERSION = '0.7.1'.freeze
3
3
 
4
4
  def gem_version
5
5
  Gem::Version.new(VERSION)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_paypal_checkout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc.
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 5.1.0
18
+ version: 5.4.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 5.1.0
25
+ version: 5.4.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: spree_admin
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 5.1.0
32
+ version: 5.4.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 5.1.0
39
+ version: 5.4.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: paypal-server-sdk
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -116,20 +116,23 @@ files:
116
116
  - README.md
117
117
  - Rakefile
118
118
  - app/assets/config/spree_paypal_checkout_manifest.js
119
- - app/controllers/spree/api/v2/storefront/paypal_orders_controller.rb
120
119
  - app/controllers/spree_paypal_checkout/store_controller_decorator.rb
121
120
  - app/helpers/spree_paypal_checkout/base_helper.rb
122
121
  - app/javascript/spree_paypal_checkout/application.js
123
122
  - app/javascript/spree_paypal_checkout/controllers/checkout_paypal_controller.js
123
+ - app/models/spree/payment_sessions/paypal_checkout.rb
124
+ - app/models/spree/payment_setup_sessions/paypal_checkout.rb
124
125
  - app/models/spree_paypal_checkout/base.rb
125
126
  - app/models/spree_paypal_checkout/gateway.rb
127
+ - app/models/spree_paypal_checkout/gateway/payment_sessions.rb
128
+ - app/models/spree_paypal_checkout/gateway/payment_setup_sessions.rb
126
129
  - app/models/spree_paypal_checkout/order.rb
127
130
  - app/models/spree_paypal_checkout/order_decorator.rb
128
131
  - app/models/spree_paypal_checkout/payment_method_decorator.rb
129
132
  - app/models/spree_paypal_checkout/payment_sources/paypal.rb
130
133
  - app/models/spree_paypal_checkout/store_decorator.rb
131
134
  - app/presenters/spree_paypal_checkout/order_presenter.rb
132
- - app/serializers/spree/api/v2/storefront/paypal_order_serializer.rb
135
+ - app/presenters/spree_paypal_checkout/setup_token_presenter.rb
133
136
  - app/services/spree_paypal_checkout/capture_order.rb
134
137
  - app/services/spree_paypal_checkout/create_payment.rb
135
138
  - app/services/spree_paypal_checkout/create_source.rb
@@ -145,6 +148,8 @@ files:
145
148
  - config/routes.rb
146
149
  - db/migrate/20250528095719_create_spree_paypal_checkout_orders.rb
147
150
  - lib/generators/spree_paypal_checkout/install/install_generator.rb
151
+ - lib/spree_api_v2/spree/api/v2/storefront/paypal_order_serializer.rb
152
+ - lib/spree_api_v2/spree/api/v2/storefront/paypal_orders_controller.rb
148
153
  - lib/spree_paypal_checkout.rb
149
154
  - lib/spree_paypal_checkout/configuration.rb
150
155
  - lib/spree_paypal_checkout/engine.rb
@@ -155,9 +160,9 @@ licenses:
155
160
  - MIT
156
161
  metadata:
157
162
  bug_tracker_uri: https://github.com/spree/spree_paypal_checkout/issues
158
- changelog_uri: https://github.com/spree/spree_paypal_checkout/releases/tag/v0.6.0
163
+ changelog_uri: https://github.com/spree/spree_paypal_checkout/releases/tag/v0.7.1
159
164
  documentation_uri: https://docs.spreecommerce.org/
160
- source_code_uri: https://github.com/spree/spree_paypal_checkout/tree/v0.6.0
165
+ source_code_uri: https://github.com/spree/spree_paypal_checkout/tree/v0.7.1
161
166
  rdoc_options: []
162
167
  require_paths:
163
168
  - lib