Module: SpreePaypalCheckout::Gateway::PaymentSessions

Extended by:
ActiveSupport::Concern
Included in:
SpreePaypalCheckout::Gateway
Defined in:
app/models/spree_paypal_checkout/gateway/payment_sessions.rb

Instance Method Summary collapse

Instance Method Details

#complete_payment_session(payment_session:, params: {}) ⇒ Object

Completes a payment session by capturing the PayPal order, creating the Payment record, and transitioning the session.

Does NOT complete the order – that is handled by Carts::Complete (called by the storefront or by the webhook handler).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 56

def complete_payment_session(payment_session:, params: {})
  paypal_order_id = payment_session.external_id

  response = client.orders.capture_order({
    'id' => paypal_order_id,
    'prefer' => 'return=representation'
  })

  # Persist capture data outside the lock so it survives if post-capture bookkeeping fails
  payment_session.update!(external_data: response.data.as_json)

  payment_session.order.with_lock do
    if response.data.status == 'COMPLETED'
      payment_session.process if payment_session.can_process?

      payment = payment_session.find_or_create_payment!

      if payment.present? && !payment.completed?
        payment.started_processing! if payment.checkout?
        payment.complete! if payment.can_complete?
      end

      create_profile(payment) if payment&.source.present?

      payment_session.complete unless payment_session.completed?
    else
      payment_session.fail if payment_session.can_fail?
    end
  end
rescue PaypalServerSdk::APIException => e
  payment_session.fail if payment_session.can_fail?
  raise Spree::Core::GatewayError, "PayPal API error: #{e.message}"
end

#create_payment_session(order:, amount: nil, external_data: {}) ⇒ Object

Creates a PayPal order via the PayPal Orders API and persists a Spree::PaymentSessions::PaypalCheckout record.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 18

def create_payment_session(order:, amount: nil, external_data: {})
  total = amount.presence || order.total_minus_store_credits

  return nil if total.zero?

  protect_from_error do
    order_presenter = SpreePaypalCheckout::OrderPresenter.new(order)
    paypal_response = client.orders.create_order(order_presenter.to_json)

    payment_session_class.create!(
      order: order,
      payment_method: self,
      amount: total,
      currency: order.currency,
      status: 'pending',
      external_id: paypal_response.data.id,
      customer: order.user,
      external_data: paypal_response.data.as_json
    )
  end
end

#parse_webhook_event(raw_body, headers) ⇒ Object

Parses incoming PayPal webhook events.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 91

def parse_webhook_event(raw_body, headers)
  verify_webhook_signature!(raw_body, headers)

  event = JSON.parse(raw_body).with_indifferent_access
  event_type = event[:event_type]
  resource = event[:resource] || {}

  paypal_order_id = extract_order_id_from_webhook(event_type, resource)
  return nil unless paypal_order_id

  payment_session = Spree::PaymentSessions::PaypalCheckout.find_by(
    payment_method: self,
    external_id: paypal_order_id
  )
  return nil unless payment_session

  case event_type
  when 'CHECKOUT.ORDER.APPROVED'
    { action: :authorized, payment_session: payment_session, metadata: { paypal_event: event } }
  when 'PAYMENT.CAPTURE.COMPLETED'
    { action: :captured, payment_session: payment_session, metadata: { paypal_event: event } }
  when 'PAYMENT.CAPTURE.DENIED', 'PAYMENT.CAPTURE.DECLINED'
    { action: :failed, payment_session: payment_session, metadata: { paypal_event: event } }
  when 'PAYMENT.CAPTURE.REVERSED', 'PAYMENT.CAPTURE.REFUNDED'
    { action: :canceled, payment_session: payment_session, metadata: { paypal_event: event } }
  else
    nil
  end
end

#payment_session_classObject



12
13
14
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 12

def payment_session_class
  Spree::PaymentSessions::PaypalCheckout
end

#session_required?Boolean

Returns:

  • (Boolean)


8
9
10
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 8

def session_required?
  !SpreePaypalCheckout::Config[:use_legacy_api]
end

#update_payment_session(payment_session:, amount: nil, external_data: {}) ⇒ Object



40
41
42
43
44
45
46
47
48
49
# File 'app/models/spree_paypal_checkout/gateway/payment_sessions.rb', line 40

def update_payment_session(payment_session:, amount: nil, external_data: {})
  attrs = {}
  attrs[:amount] = amount if amount.present?

  if external_data.present?
    attrs[:external_data] = (payment_session.external_data || {}).merge(external_data.stringify_keys)
  end

  payment_session.update!(attrs) if attrs.any?
end