Class: Payment
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Payment
- Includes:
- Models::Auditable, PgSearch::Model
- Defined in:
- app/models/payment.rb
Overview
== Schema Information
Table name: payments
Database name: primary
id :bigint not null, primary key
account_holder_type :string
account_type :string
address_line1_check :string
address_zip_check :string
amazon_pay_status :string
amount :decimal(, )
approval_notification_sent :boolean
authorization_code :string
authorization_reference :string
authorization_type :string
billing_address_city :string
billing_address_country :string
billing_address_line1 :string
billing_address_line2 :string
billing_address_state :string
billing_address_zip :string
brand :string
bread_token :string
capture_before :datetime
card_country :string
card_expires_on :date
card_identifier :string
card_type :string
category :string
currency :string
cvc_check :string
date :date
email :string
exp_month :integer
exp_year :integer
first_name :string
fraud_review_done :boolean
http_accept_language :string
http_user_agent :string
issuer_number :string
last4 :string
last_name :string
name :string
payment_approved :boolean
paypal_email :string
paypal_metadata :jsonb
paypal_token :string
plaid_expected_settlement_date :date
plaid_public_token :string
po_number :string
radar_network_status :string
radar_reason :string
radar_risk_level :string
radar_seller_message :string
radar_type :string
reference :string
remote_ip_address :string
routing_number :string
send_authorization_email :boolean
shipping_address_city :string
shipping_address_country :string
shipping_address_line1 :string
shipping_address_line2 :string
shipping_address_name :string
shipping_address_state :string
shipping_address_zip :string
skip_auto_receipt :boolean default(FALSE)
skip_minfraud :boolean
state :string default("pending")
stripe_capabilities :jsonb
test :boolean
uploads_count :integer
zip_code :string
created_at :datetime not null
updated_at :datetime not null
account_id :integer
amazon_pay_charge_id :string
amazon_pay_charge_permission_id :string
amazon_pay_checkout_session_id :string
creator_id :integer
credit_memo_id :integer
customer_id :integer
delivery_id :integer
invoice_id :integer
order_id :integer
paypal_payer_id :string
paypal_transaction_id :string
plaid_account_id :string
plaid_transfer_id :string
plaid_transfer_intent_id :string
rma_id :integer
stripe_payment_intent_id :string
transaction_id :string
updater_id :integer
vault_id :string
vpo_contact_id :integer
Indexes
by_did_ctry_st (delivery_id,category,state)
by_iid_st_at (invoice_id,state,authorization_type)
by_oid_ctry_st (order_id,category,state)
idx_state_category (state,category)
index_payments_on_account_id (account_id)
index_payments_on_authorization_type_and_authorization_code (authorization_type,authorization_code) WHERE ((authorization_type IS NOT NULL) AND (authorization_code IS NOT NULL))
index_payments_on_creator_id (creator_id)
index_payments_on_credit_memo_id (credit_memo_id)
index_payments_on_customer_id (customer_id)
index_payments_on_order_id_and_state (order_id,state)
index_payments_on_paypal_payer_id (paypal_payer_id)
index_payments_on_paypal_transaction_id (paypal_transaction_id)
index_payments_on_rma_id (rma_id)
index_payments_on_stripe_payment_intent_id (stripe_payment_intent_id) WHERE (stripe_payment_intent_id IS NOT NULL)
index_payments_on_transaction_id (transaction_id)
index_payments_on_updater_id (updater_id)
index_payments_on_vault_id (vault_id)
index_payments_on_vpo_contact_id (vpo_contact_id)
Foreign Keys
payments_credit_memo_id_fkey (credit_memo_id => credit_memos.id)
payments_customer_id_fkey (customer_id => parties.id) ON DELETE => cascade
payments_delivery_id_fk (delivery_id => deliveries.id) ON DELETE => nullify
payments_invoice_id_fkey (invoice_id => invoices.id)
payments_order_id_fk (order_id => orders.id) ON DELETE => cascade
Defined Under Namespace
Classes: DailyIssuesDigestWorker, DuplicateChargeGuard, OrderProcessor, PaypalStatusResult, StrategyResolver, StripeRefundReconciliationWorker
Constant Summary collapse
- ADV_REPL =
'Advance Replacement'- CHECK =
'Check'- CREDIT_CARD =
'Credit Card'- CREDIT_CARD_TERMINAL =
'Credit Card Terminal'- BREAD =
'Bread'- PLAID =
'Plaid'- AMAZON_PAY =
'Amazon Pay'- PO =
'Purchase Order'- VPO =
'Verbal Purchase Order'- ECHECK =
'eCheck'- PAYPAL =
'PayPal'- PAYPAL_INVOICE =
'PayPal Invoice'- RMA_CREDIT =
'RMA Credit'- CASH =
'Cash'- STORE_CREDIT =
'Store Credit'- WIRE =
'Wire Transfer'- ACCOUNT_HOLDER_TYPES =
%w[personal business]
- ACCOUNT_TYPES =
%w[checking savings]
- PAYPAL_MIN_SIGNATURE_REQUIRED =
750.00- ECHECK_MIN_AMOUNT_WITHOUT_SUPERVISION =
2000.00- CATEGORIES_REQUIRING_REVIEW =
[PAYPAL_INVOICE, RMA_CREDIT, ECHECK, CHECK, CASH, WIRE]
- CATEGORIES_NOT_ALLOWING_CAPTURE =
[PO, VPO, ECHECK, WIRE]
- PAYPAL_OVER_CAPTURE_PERCENT =
BigDecimal('1.15')
- PAYPAL_OVER_CAPTURE_MAX_INCREASE =
BigDecimal('75')
- PAYPAL_HONOR_PERIOD =
3.days
- PAYPAL_REAUTH_BUFFER =
12.hours
- PAYPAL_MAX_AUTH_DAYS =
29- PAYPAL_REAUTH_EARLIEST_DAY =
4
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #account_holder_type ⇒ Object readonly
-
#account_number ⇒ Object
Returns the value of attribute account_number.
- #account_type ⇒ Object readonly
-
#address_city ⇒ Object
Returns the value of attribute address_city.
-
#address_country ⇒ Object
Returns the value of attribute address_country.
-
#address_id ⇒ Object
Returns the value of attribute address_id.
-
#address_line1 ⇒ Object
Returns the value of attribute address_line1.
-
#address_line2 ⇒ Object
Returns the value of attribute address_line2.
-
#address_state ⇒ Object
Returns the value of attribute address_state.
-
#address_zip ⇒ Object
Returns the value of attribute address_zip.
- #amount ⇒ Object readonly
-
#amount_to_capture ⇒ Object
Returns the value of attribute amount_to_capture.
-
#bread_token ⇒ Object
Returns the value of attribute bread_token.
-
#card_token ⇒ Object
Returns the value of attribute card_token.
- #category ⇒ Object readonly
-
#consent_channel ⇒ Object
Returns the value of attribute consent_channel.
- #currency ⇒ Object readonly
- #email ⇒ Object readonly
-
#error_codes ⇒ Object
Returns the value of attribute error_codes.
-
#issuer_number ⇒ Object
Returns the value of attribute issuer_number.
-
#last_response ⇒ Object
Returns the value of attribute last_response.
- #paypal_email ⇒ Object readonly
- #po_number ⇒ Object readonly
- #rma_id ⇒ Object readonly
- #state ⇒ Object readonly
-
#store_address ⇒ Object
Returns the value of attribute store_address.
-
#store_card ⇒ Object
Returns the value of attribute store_card.
-
#store_card_name ⇒ Object
Returns the value of attribute store_card_name.
- #vpo_contact_id ⇒ Object readonly
Belongs to collapse
- #account ⇒ Account
- #credit_card_vault ⇒ CreditCardVault
- #credit_memo ⇒ CreditMemo
- #customer ⇒ Customer
- #delivery ⇒ Delivery
- #invoice ⇒ Invoice
- #legacy_order ⇒ Order
- #order ⇒ Order
- #rma ⇒ Rma
- #vpo_contact ⇒ Contact
Methods included from Models::Auditable
Has one collapse
Has many collapse
- #receipts ⇒ ActiveRecord::Relation<Receipt>
- #transactions ⇒ ActiveRecord::Relation<OrderTransaction>
- #uploads ⇒ ActiveRecord::Relation<Upload>
Class Method Summary collapse
-
.all_amazon_pay_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all amazon pay captured.
-
.all_authorized ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all authorized.
-
.all_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all captured.
-
.all_cc_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all cc captured.
-
.all_check_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all check captured.
-
.all_collect_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all collect captured.
-
.all_paypal_invoice_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all paypal invoice captured.
-
.all_plaid_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all plaid captured.
-
.amazon_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are amazon payments.
-
.bread_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are bread payments.
-
.can_be_refunded ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are can be refunded.
-
.cc_paypal_bread_amazon ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are cc paypal bread amazon.
-
.check_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are check payments.
-
.credit_cards ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are credit cards.
-
.echeck_payment_review(amount, customer, order) ⇒ Hash{Symbol=>Boolean,Array<String>}
Decide whether an eCheck of
amountforcustomer/orderrequires manual approval. -
.expired ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are expired.
-
.non_voided ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are non voided.
-
.order ⇒ Order?
NOTE: this class-level shadow is highly suspect — it would call itself recursively.
-
.payment_options(customer, order = nil, currency = nil) ⇒ Array<String>
Categories the customer is allowed to pick from in the new-payment form.
-
.paypal_invoices ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are paypal invoices.
-
.paypal_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are paypal payments.
-
.plaid_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are plaid payments.
-
.po_search ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are po search.
-
.purchase_orders ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are purchase orders.
-
.wire_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are wire payments.
Instance Method Summary collapse
-
#all_pi_siblings ⇒ ActiveRecord::Relation<Payment>
All other payments (any state) sharing this PI.
-
#amount_captured_on_paypal ⇒ Float, false
Amount PayPal reports captured against this authorization (dollar value, not cents).
-
#amount_captured_on_stripe ⇒ Float, false
Cents-or-dollars amount Stripe reports as captured on the upstream PI/charge.
-
#attempt_paypal_reauthorization_if_needed(auth_expiration) ⇒ Payment::PaypalStatusResult
Decide whether to reauthorize this PayPal payment now.
-
#auth_code ⇒ String?
Card-network auth code (the 6-digit code the issuer returned on capture).
-
#auth_url ⇒ String?
Deep link to the auth/PI on the upstream gateway dashboard (PayPal, Stripe live, or Stripe test).
-
#authorization_review ⇒ Hash{Symbol=>Boolean,Array<String>}
Decide whether this payment needs Accounting approval before being treated as authorized.
- #authorization_review_required? ⇒ Boolean
- #automatically_authorized? ⇒ Boolean
-
#available_to_refund ⇒ BigDecimal, Float
Cash still available to refund: captured minus already refunded.
-
#billing_address ⇒ Address
Build a transient Address from the captured billing fields (
billing_address_*columns). - #can_be_voided? ⇒ Boolean
-
#capture_deadline ⇒ ActiveSupport::TimeWithZone?
Hard deadline by which the auth must be captured (or reauthorized); delegates to the StrategyResolver for gateway-specific rules.
- #captured_on_paypal? ⇒ Boolean
- #captured_on_stripe? ⇒ Boolean
-
#check_cc_payment_status ⇒ void
Reconcile a Stripe credit-card payment with the upstream Payment Intent: void on canceled, sync capture on succeeded, trigger reauthorization when nearing the deadline, and fall back to legacy charge handling when the object isn't a PI.
-
#check_paypal_invoice_payment_status ⇒ Boolean?
Check PayPal for whether a hosted invoice has been paid yet; if so capture locally and (when every PayPal-invoice payment on the order is captured) release the order.
-
#check_paypal_payment_status ⇒ Payment::PaypalStatusResult
Re-poll PayPal for the current state of this authorization and reconcile the local payment: capture externally, void, expire, decline, or attempt a reauthorization, depending on what PayPal reports.
-
#communication_resource ⇒ Order
Resource the audit/comm subsystem keys off — for payments, that's the parent Order.
-
#crm_link ⇒ String
CRM order URL for this payment, swallowing routing errors so audit exports never blow up on a bad route.
-
#currency_symbol ⇒ String
Currency symbol used in CRM/invoice displays.
-
#deep_dup ⇒ Payment
Clone the payment, including transactions/uploads via deep_clone, but drop the Delivery association so the copy can be re-attached on the target shipment without violating the unique pairing.
-
#default_cc_options(ip_address = nil, email = nil) ⇒ Hash
ActiveMerchant-style options hash used when authorizing/capturing against the card gateway from an order context.
-
#default_echeck_options(_ip_address = nil) ⇒ Hash
Forte-formatted options hash used when authorizing an eCheck.
-
#default_paypal_options ⇒ Hash
Minimal options hash for PayPal API calls (just currency for now).
-
#detect_fraud(force_new_report: false) ⇒ Order::FraudDetector::Result?
Run the fraud detector for credit card / PayPal / eCheck payments.
- #does_not_allow_capture? ⇒ Boolean
-
#email_collection_for_select ⇒ Array<String>
Email options for the "send receipt to" select on the payment form: every email on the customer plus the address captured on the payment itself, deduped.
-
#full_card_number ⇒ String?
Reconstruct a masked PAN for display: BIN + xxx + last4 if both are known, otherwise just the last 4 with a generic mask.
-
#full_name ⇒ String
Cardholder/payer full name from the captured first_name/last_name pair.
-
#full_name=(value) ⇒ Object
Split a free-form name string via PersonNameParser and assign
first_name/last_nameaccordingly. - #funds_fully_refunded? ⇒ Boolean
- #funds_partially_refunded? ⇒ Boolean
-
#gateway_class ⇒ Class
Strategy class used to authorize/capture/void this payment.
- #is_advanced_replacement? ⇒ Boolean
- #is_amazon_pay? ⇒ Boolean
- #is_crm_legacy_vault? ⇒ Boolean
- #is_plaid? ⇒ Boolean
- #is_po? ⇒ Boolean
- #is_receipt_skippable? ⇒ Boolean
- #is_rma_credit? ⇒ Boolean
- #is_store_credit? ⇒ Boolean
- #is_www_apple_pay? ⇒ Boolean
-
#last_authorization_message ⇒ String?
Message text on the most recent OrderTransaction (typically the gateway's last response message).
-
#paypal_over_capture_headroom ⇒ BigDecimal
How much more is still capturable on this PayPal auth before hitting #paypal_over_capture_limit, considering every active sibling payment.
-
#paypal_over_capture_limit ⇒ BigDecimal?
Maximum total that can be captured against this PayPal auth: the smaller of 115% of the original total and original + $75.
-
#paypal_shared_auth_total ⇒ BigDecimal?
Original total at the time the PayPal authorization was first established (sum of every sibling's amount).
- #pending_release_authorization? ⇒ Boolean
-
#po_upload ⇒ Upload?
First non-deleted upload tagged
purchase_order— the customer's PO PDF supporting a PO payment. -
#process_tx_results(tx) ⇒ Object
Copy card metadata, billing/shipping addresses, and Stripe Radar signals from a successful Stripe OrderTransaction (or the linked CreditCardVault) onto this payment so the CRM can show full context without re-querying Stripe.
- #refunded_on_stripe? ⇒ Boolean
-
#resend_paypal_invoice ⇒ Hash{Symbol=>Object}
Re-send the PayPal invoice reminder email via the PayPal API.
-
#send_authorization_email_notification ⇒ Communication, String
Send the cardholder a "we authorized your card" email.
-
#send_wire_info_email ⇒ Communication, String
Send the customer the WIRE_TRANSFER_INFO email with bank details they need to wire funds.
- #shared_paypal_auth? ⇒ Boolean
-
#shared_paypal_auth_siblings ⇒ ActiveRecord::Relation<Payment>
Other authorized PayPal payments sharing this PayPal authorization id (PayPal supports up to 115% over-capture across siblings).
- #shared_pi? ⇒ Boolean
-
#shared_pi_siblings ⇒ ActiveRecord::Relation<Payment>
Other authorized payments that share this Stripe payment intent (split orders / partial captures stack on one PI).
-
#shipping_address ⇒ Address
Build a transient Address from the captured shipping fields.
-
#stripe_api_key ⇒ String
Stripe API key appropriate for this payment's currency (the codebase routes USD through one Stripe account, CAD through another).
-
#stripe_payment_object ⇒ Stripe::PaymentIntent, ...
(also: #stripe_charge)
Fetch the upstream Stripe object (Charge or PaymentIntent) for this payment.
-
#stripe_resolver ⇒ Payment::StrategyResolver
Memoised Stripe capture/reauth strategy resolver — encapsulates the capture window math (Stripe legacy 7d, extended 30d, PayPal 29d).
- #supports_extended_authorization? ⇒ Boolean
- #supports_incremental_authorization? ⇒ Boolean
- #supports_multicapture? ⇒ Boolean
-
#sync_capture_before_from_paypal_expiry(auth_expiration) ⇒ Object
Push
capture_beforeforward when PayPal hands back a laterexpiration_timethan we have stored — this keeps the local capture-window cache in sync with PayPal's truth. -
#sync_external_capture(pi) ⇒ Object
Reflect a Stripe-side capture (or release) of a Payment Intent into this Payment.
-
#sync_paypal_authorization_expiry(auth_expiration) ⇒ Object
Persist PayPal's authoritative authorization expiry into
paypal_metadata['authorization_expiry']so callers don't have to re-poll on every action. -
#sync_paypal_external_capture(auth_response) ⇒ Object
Record a capture that happened on the PayPal dashboard rather than through Heatwave: append a synthetic capture OrderTransaction and advance the state to captured.
-
#to_s ⇒ String
"Payment 1234 (ORD-555)".
-
#total_authorized ⇒ Float
Live authorization total in dollars.
-
#total_captured ⇒ Float
Sum of all successful capture/purchase/settle transactions in dollars (gateway records cents).
-
#total_refunded ⇒ Float
Sum of all successful refund transactions in dollars.
-
#transaction_reference(action) ⇒ String?
Reference returned by the gateway for the first successful transaction of
action(e.g. authorization, capture, refund). -
#update_authorization_code(action) ⇒ Object
Refresh
authorization_codefrom the latest successful transaction of the given action (e.g.'authorization','reauthorization').
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#account_holder_type ⇒ Object (readonly)
186 |
# File 'app/models/payment.rb', line 186 validates :account_type, :account_holder_type, presence: { if: proc { |pp| pp.vault_id.blank? && pp. == 'check' } } |
#account_number ⇒ Object
Returns the value of attribute account_number.
212 213 214 |
# File 'app/models/payment.rb', line 212 def account_number @account_number end |
#account_type ⇒ Object (readonly)
186 |
# File 'app/models/payment.rb', line 186 validates :account_type, :account_holder_type, presence: { if: proc { |pp| pp.vault_id.blank? && pp. == 'check' } } |
#address_city ⇒ Object
Returns the value of attribute address_city.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_city @address_city end |
#address_country ⇒ Object
Returns the value of attribute address_country.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_country @address_country end |
#address_id ⇒ Object
Returns the value of attribute address_id.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_id @address_id end |
#address_line1 ⇒ Object
Returns the value of attribute address_line1.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_line1 @address_line1 end |
#address_line2 ⇒ Object
Returns the value of attribute address_line2.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_line2 @address_line2 end |
#address_state ⇒ Object
Returns the value of attribute address_state.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_state @address_state end |
#address_zip ⇒ Object
Returns the value of attribute address_zip.
212 213 214 |
# File 'app/models/payment.rb', line 212 def address_zip @address_zip end |
#amount ⇒ Object (readonly)
182 |
# File 'app/models/payment.rb', line 182 validates :category, :amount, :currency, :state, presence: true |
#amount_to_capture ⇒ Object
Returns the value of attribute amount_to_capture.
212 213 214 |
# File 'app/models/payment.rb', line 212 def amount_to_capture @amount_to_capture end |
#bread_token ⇒ Object
Returns the value of attribute bread_token.
212 213 214 |
# File 'app/models/payment.rb', line 212 def bread_token @bread_token end |
#card_token ⇒ Object
Returns the value of attribute card_token.
212 213 214 |
# File 'app/models/payment.rb', line 212 def card_token @card_token end |
#category ⇒ Object (readonly)
182 |
# File 'app/models/payment.rb', line 182 validates :category, :amount, :currency, :state, presence: true |
#consent_channel ⇒ Object
Returns the value of attribute consent_channel.
212 213 214 |
# File 'app/models/payment.rb', line 212 def @consent_channel end |
#currency ⇒ Object (readonly)
182 |
# File 'app/models/payment.rb', line 182 validates :category, :amount, :currency, :state, presence: true |
#email ⇒ Object (readonly)
206 |
# File 'app/models/payment.rb', line 206 validates :email, email_format: true |
#error_codes ⇒ Object
Returns the value of attribute error_codes.
212 213 214 |
# File 'app/models/payment.rb', line 212 def error_codes @error_codes end |
#issuer_number ⇒ Object
Returns the value of attribute issuer_number.
212 213 214 |
# File 'app/models/payment.rb', line 212 def issuer_number @issuer_number end |
#last_response ⇒ Object
Returns the value of attribute last_response.
212 213 214 |
# File 'app/models/payment.rb', line 212 def last_response @last_response end |
#paypal_email ⇒ Object (readonly)
187 |
# File 'app/models/payment.rb', line 187 validates :paypal_email, presence: { if: proc { |pp| pp. == 'paypal_invoice' } } |
#po_number ⇒ Object (readonly)
184 |
# File 'app/models/payment.rb', line 184 validates :po_number, presence: { if: proc { |pp| pp.category == PO } } |
#rma_id ⇒ Object (readonly)
185 |
# File 'app/models/payment.rb', line 185 validates :rma_id, presence: { if: proc { |pp| pp.category == ADV_REPL } } |
#state ⇒ Object (readonly)
182 |
# File 'app/models/payment.rb', line 182 validates :category, :amount, :currency, :state, presence: true |
#store_address ⇒ Object
Returns the value of attribute store_address.
212 213 214 |
# File 'app/models/payment.rb', line 212 def store_address @store_address end |
#store_card ⇒ Object
Returns the value of attribute store_card.
212 213 214 |
# File 'app/models/payment.rb', line 212 def store_card @store_card end |
#store_card_name ⇒ Object
Returns the value of attribute store_card_name.
212 213 214 |
# File 'app/models/payment.rb', line 212 def store_card_name @store_card_name end |
#vpo_contact_id ⇒ Object (readonly)
183 |
# File 'app/models/payment.rb', line 183 validates :vpo_contact_id, presence: { if: proc { |pp| pp.category == VPO } } |
Class Method Details
.all_amazon_pay_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all amazon pay captured. Active Record Scope
234 |
# File 'app/models/payment.rb', line 234 scope :all_amazon_pay_captured, -> { all_captured.where(authorization_type: 'amazon_pay') } |
.all_authorized ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all authorized. Active Record Scope
237 |
# File 'app/models/payment.rb', line 237 scope :all_authorized, -> { where(state: 'authorized') } |
.all_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all captured. Active Record Scope
229 |
# File 'app/models/payment.rb', line 229 scope :all_captured, -> { where(state: 'captured') } |
.all_cc_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all cc captured. Active Record Scope
231 |
# File 'app/models/payment.rb', line 231 scope :all_cc_captured, -> { all_captured.where(authorization_type: %w[credit_card paypal]) } |
.all_check_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all check captured. Active Record Scope
232 |
# File 'app/models/payment.rb', line 232 scope :all_check_captured, -> { all_captured.where(authorization_type: 'check') } |
.all_collect_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all collect captured. Active Record Scope
236 |
# File 'app/models/payment.rb', line 236 scope :all_collect_captured, -> { all_captured.where(authorization_type: 'credit_card', reference: 'Collect Card Reader') } |
.all_paypal_invoice_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all paypal invoice captured. Active Record Scope
235 |
# File 'app/models/payment.rb', line 235 scope :all_paypal_invoice_captured, -> { all_captured.where(authorization_type: 'paypal_invoice') } |
.all_plaid_captured ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are all plaid captured. Active Record Scope
233 |
# File 'app/models/payment.rb', line 233 scope :all_plaid_captured, -> { all_captured.where(authorization_type: 'plaid') } |
.amazon_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are amazon payments. Active Record Scope
219 |
# File 'app/models/payment.rb', line 219 scope :amazon_payments, -> { where(category: AMAZON_PAY) } |
.bread_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are bread payments. Active Record Scope
218 |
# File 'app/models/payment.rb', line 218 scope :bread_payments, -> { where(category: BREAD) } |
.can_be_refunded ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are can be refunded. Active Record Scope
238 |
# File 'app/models/payment.rb', line 238 scope :can_be_refunded, -> { where(state: %w[captured partially_refunded]) } |
.cc_paypal_bread_amazon ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are cc paypal bread amazon. Active Record Scope
230 |
# File 'app/models/payment.rb', line 230 scope :cc_paypal_bread_amazon, -> { where(authorization_type: %w[credit_card paypal bread, amazon_pay]) } |
.check_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are check payments. Active Record Scope
221 |
# File 'app/models/payment.rb', line 221 scope :check_payments, -> { where(category: CHECK) } |
.credit_cards ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are credit cards. Active Record Scope
225 |
# File 'app/models/payment.rb', line 225 scope :credit_cards, -> { where(category: CREDIT_CARD) } |
.echeck_payment_review(amount, customer, order) ⇒ Hash{Symbol=>Boolean,Array<String>}
Decide whether an eCheck of amount for customer/order requires
manual approval. Enforces daily company-wide and per-customer caps,
the $10k upper bound, and routes through CustomerFinancials#request_credit
for credit-limit checks.
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 |
# File 'app/models/payment.rb', line 860 def self.echeck_payment_review(amount, customer, order) res = {} fails = 0 fail_reasons = [] pass_reasons = [] if Payment.where(category: ECHECK)..where('payments.created_at > ?', Time.current.at_midnight).count > 5 fails += 1 fail_reasons << 'Limit exceeded for number of automatic eCheck approvals in one day, company wide (5).' end if Payment.joins(:order).where(category: ECHECK, orders: { customer_id: order.customer_id })..where('payments.created_at > ?', Time.current.at_midnight).count > 2 fails += 1 fail_reasons << 'Limit exceeded for number of automatic eCheck approvals in one day, per customer (2).' end if amount < 100 pass_reasons = ['Amount less than $100'] else if amount > 10_000 fails += 1 fail_reasons << 'Amount greater than $10,000' end rc_res = customer.request_credit(amount) if rc_res[:approved] == false fails += rc_res[:fails] fail_reasons += rc_res[:fail_reasons] else pass_reasons = rc_res[:pass_reasons] end end if fails > 0 res[:required] = true res[:fail_reasons] = fail_reasons else res[:required] = false res[:pass_reasons] = pass_reasons end res end |
.expired ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are expired. Active Record Scope
227 |
# File 'app/models/payment.rb', line 227 scope :expired, -> { where(state: 'expired') } |
.non_voided ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are non voided. Active Record Scope
239 |
# File 'app/models/payment.rb', line 239 scope :non_voided, -> { where.not(state: %w[voided expired]) } |
.order ⇒ Order?
NOTE: this class-level shadow is highly suspect — it would call
itself recursively. Kept here because it is referenced from older
reports; treat as effectively dead until reviewed.
1164 1165 1166 |
# File 'app/models/payment.rb', line 1164 def self.order legacy_order || order end |
.payment_options(customer, order = nil, currency = nil) ⇒ Array<String>
Categories the customer is allowed to pick from in the new-payment
form. Adds PO/VPO when the customer has terms, ADV_REPL when an RMA
has credit available, RMA_CREDIT for precreated-RMA orders, ECHECK
for USD orders, and STORE_CREDIT only when the customer actually has
an applicable CreditMemo.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'app/models/payment.rb', line 335 def self.(customer, order = nil, currency = nil) = [CHECK, WIRE, CREDIT_CARD, PAYPAL, AMAZON_PAY, PAYPAL_INVOICE, CASH] if customer.has_terms? << PO # Use exists? instead of any? for better performance (single COUNT query vs loading records) << VPO if customer.contacts.verbal_po_contacts.exists? end << ADV_REPL if order && order.rma.present? && order.rma.credit_available > 0 << RMA_CREDIT if order && order.precreate_rma? << ECHECK if (order && order.currency == 'USD') || currency == 'USD' # Use exists? and check store credit in single condition to avoid loading all credit memos << STORE_CREDIT if customer.available_store_credit.to_d > 0 && customer.credit_memos.available_to_apply.exists? .sort end |
.paypal_invoices ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are paypal invoices. Active Record Scope
226 |
# File 'app/models/payment.rb', line 226 scope :paypal_invoices, -> { where(category: PAYPAL_INVOICE) } |
.paypal_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are paypal payments. Active Record Scope
223 |
# File 'app/models/payment.rb', line 223 scope :paypal_payments, -> { where(category: PAYPAL) } |
.plaid_payments ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are plaid payments. Active Record Scope
220 |
# File 'app/models/payment.rb', line 220 scope :plaid_payments, -> { where(category: PLAID) } |
.po_search ⇒ ActiveRecord::Relation<Payment>
A relation of Payments that are po search. Active Record Scope
228 |
# File 'app/models/payment.rb', line 228 scope :po_search, ->(term) { where(Payment[:po_number].matches("%#{term}%")) } |
Instance Method Details
#all_pi_siblings ⇒ ActiveRecord::Relation<Payment>
All other payments (any state) sharing this PI. Used when reconciling
external Stripe captures across split orders.
413 414 415 416 417 418 |
# File 'app/models/payment.rb', line 413 def all_pi_siblings return Payment.none unless stripe_payment_intent_id.present? Payment.where(stripe_payment_intent_id: stripe_payment_intent_id) .where.not(id: id) end |
#amount_captured_on_paypal ⇒ Float, false
Amount PayPal reports captured against this authorization (dollar
value, not cents). False for non-PayPal payments.
1152 1153 1154 1155 1156 1157 |
# File 'app/models/payment.rb', line 1152 def amount_captured_on_paypal return false if != 'paypal' auth_details = Payment::Apis::Paypal.(self).parse auth_details["amount"]["value"].to_f end |
#amount_captured_on_stripe ⇒ Float, false
Cents-or-dollars amount Stripe reports as captured on the upstream
PI/charge. Returns false for non-CC payments or on lookup failure
so callers can next unless.
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 |
# File 'app/models/payment.rb', line 1127 def amount_captured_on_stripe return false if != 'credit_card' obj = stripe_payment_object return false unless obj if Payment::Apis::Stripe.payment_intent?() obj.amount_received.to_f else obj.try(:amount_captured).to_f rescue false end end |
#attempt_paypal_reauthorization_if_needed(auth_expiration) ⇒ Payment::PaypalStatusResult
Decide whether to reauthorize this PayPal payment now. Returns
early with :still_valid when the auth or honor period still has
capture buffer; flips to expired and notifies AR/admin if past the
29-day hard limit; otherwise calls
Payment::Gateways::Paypal#reauthorize and surfaces the outcome.
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 |
# File 'app/models/payment.rb', line 655 def (auth_expiration) if auth_expiration.present? && auth_expiration > PAYPAL_REAUTH_BUFFER.from_now sync_capture_before_from_paypal_expiry(auth_expiration) hours_left = ((auth_expiration - Time.current) / 1.hour).round(1) return PaypalStatusResult.new(status: :still_valid, message: "PayPal authorization still valid (#{hours_left} hours remaining, expires #{auth_expiration.strftime('%b %d %H:%M %Z')})") end honor_deadline = capture_before || (created_at + PAYPAL_HONOR_PERIOD) if honor_deadline > PAYPAL_REAUTH_BUFFER.from_now hours_left = ((honor_deadline - Time.current) / 1.hour).round(1) return PaypalStatusResult.new(status: :still_valid, message: "Honor period still valid (#{hours_left} hours remaining, capture by #{honor_deadline.strftime('%b %d %H:%M %Z')})") end if created_at < PAYPAL_MAX_AUTH_DAYS.days.ago # No per-payment email — accounting picks this up via the nightly # Payment::DailyIssuesDigestWorker, which scans for PayPal # payments that transitioned to `expired` in the last 24 hours. logger.error("Payment #{id}: PayPal authorization past #{PAYPAL_MAX_AUTH_DAYS}-day limit, cannot reauthorize") order.cr_hold if order.present? payment_expired! return PaypalStatusResult.new(status: :expired, message: "Authorization past #{PAYPAL_MAX_AUTH_DAYS}-day limit") end days_since_auth = ((Time.current - created_at) / 1.day).floor if days_since_auth < PAYPAL_REAUTH_EARLIEST_DAY return PaypalStatusResult.new(status: :still_valid, message: "Too early to reauthorize (day #{days_since_auth} of #{PAYPAL_REAUTH_EARLIEST_DAY} minimum). Authorization is still capturable.") end res = Payment::Gateways::Paypal.new(self).(amount) if res.success PaypalStatusResult.new(status: :reauthorized, message: 'Successfully reauthorized') else # No per-payment email — accounting picks this up via the nightly # Payment::DailyIssuesDigestWorker, which scans for # action='reauthorization' OrderTransactions with success=false # in the last 24 hours. logger.error("#{Time.current}: PAYPAL REAUTHORIZATION ERROR: Problem reauthorizing paypal payment.") PaypalStatusResult.new(status: :reauth_failed, message: res. || 'Reauthorization failed') end end |
#auth_code ⇒ String?
Card-network auth code (the 6-digit code the issuer returned on
capture). Read from the first capture transaction's params.
1044 1045 1046 1047 |
# File 'app/models/payment.rb', line 1044 def auth_code capture = transactions.where(action: 'capture', success: true).first capture.nil? ? nil : capture&.params&.[]('auth_code') end |
#auth_url ⇒ String?
Deep link to the auth/PI on the upstream gateway dashboard
(PayPal, Stripe live, or Stripe test). Returns nil when there is
no auth yet.
1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 |
# File 'app/models/payment.rb', line 1386 def auth_url return nil if .nil? if == 'paypal' host = test? ? "www.sandbox.paypal.com" : "www.paypal.com" "https://#{host}/activity/payment/#{}" elsif test? "https://dashboard.stripe.com/test/payments/#{}" else "https://dashboard.stripe.com/payments/#{}" end end |
#authorization_review ⇒ Hash{Symbol=>Boolean,Array<String>}
Decide whether this payment needs Accounting approval before being
treated as authorized. Returns a hash with :required and lists
of :fail_reasons/:pass_reasons strings used by the CRM review
UI.
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'app/models/payment.rb', line 495 def res = {} case category when PAYPAL_INVOICE, RMA_CREDIT res[:required] = true res[:fail_reasons] = ["#{category} payment requires Accounting approval"] when ECHECK # This branch gates *order release* on the order's total authorized # eChecks. A receipt-form eCheck (paying invoices directly) has no # order to release, so there is nothing to review — and dereferencing # `order` here raised `NoMethodError: undefined method 'payments' for # nil` when viewing /payments/:id/transactions (AppSignal, payment # 285034: orderless eCheck for customer 98660). if order echeck_total = order.payments..where(category: ECHECK).sum(:amount) echeck_res = Payment.echeck_payment_review(echeck_total, order.customer, order) res[:required] = echeck_res[:required] res[:fail_reasons] = echeck_res[:fail_reasons] res[:pass_reasons] = echeck_res[:pass_reasons] else res[:required] = false end when CHECK, WIRE res[:required] = true res[:fail_reasons] = ['All Check/Wire payments requires Accounting approval'] when CASH if order.nil? # Orderless cash (receipt-form payment): no order release to gate. res[:required] = false elsif order.is_warehouse_pickup? if amount > 100 # pickups where cash amount is greater than $100 require authorization res[:required] = true res[:fail_reasons] = ["Pickup order with #{category} payment more than $100 requires Accounting approval"] else res[:required] = false res[:pass_reasons] = ["Pickup order with #{category} payment more than $100 requires Accounting approval"] end else # if it's not a pickup, then cash always require authorization res[:required] = true res[:fail_reasons] = ["Non-pickup order with #{category} payment requires Accounting approval"] end else # any other payment method doesn't require authorization res[:required] = false end res end |
#authorization_review_required? ⇒ Boolean
485 486 487 |
# File 'app/models/payment.rb', line 485 def CATEGORIES_REQUIRING_REVIEW.include?(category) end |
#automatically_authorized? ⇒ Boolean
545 546 547 |
# File 'app/models/payment.rb', line 545 def and [:required] == false end |
#available_to_refund ⇒ BigDecimal, Float
Cash still available to refund: captured minus already refunded.
1171 1172 1173 |
# File 'app/models/payment.rb', line 1171 def available_to_refund total_captured - total_refunded end |
#billing_address ⇒ Address
Build a transient Address from the captured billing fields
(billing_address_* columns). Used for AVS displays and receipts.
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 |
# File 'app/models/payment.rb', line 1413 def billing_address Address.new( street1: billing_address_line1, street2: billing_address_line2, city: billing_address_city, zip: billing_address_zip, state_code: State.code_for_string(billing_address_state), country_iso3: Country.iso3_for_string(billing_address_country) ) end |
#can_be_voided? ⇒ Boolean
393 394 395 |
# File 'app/models/payment.rb', line 393 def can_be_voided? ( and !order.editing_locked? and category != 'Credit Card Terminal') end |
#capture_deadline ⇒ ActiveSupport::TimeWithZone?
Hard deadline by which the auth must be captured (or reauthorized);
delegates to the StrategyResolver for gateway-specific
rules.
389 390 391 |
# File 'app/models/payment.rb', line 389 def capture_deadline stripe_resolver. end |
#captured_on_paypal? ⇒ Boolean
1140 1141 1142 1143 1144 1145 1146 |
# File 'app/models/payment.rb', line 1140 def captured_on_paypal? return false if != 'paypal' auth_details = Payment::Apis::Paypal.(self).parse auth_status = auth_details['status'] auth_status == 'CAPTURED' end |
#captured_on_stripe? ⇒ Boolean
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 |
# File 'app/models/payment.rb', line 1109 def captured_on_stripe? return false if != 'credit_card' obj = stripe_payment_object return false unless obj if Payment::Apis::Stripe.payment_intent?() obj.status == 'succeeded' else obj.try(:captured?) rescue false end end |
#check_cc_payment_status ⇒ void
This method returns an undefined value.
Reconcile a Stripe credit-card payment with the upstream Payment
Intent: void on canceled, sync capture on succeeded, trigger
reauthorization when nearing the deadline, and fall back to legacy
charge handling when the object isn't a PI.
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 |
# File 'app/models/payment.rb', line 714 def check_cc_payment_status return unless return unless == 'credit_card' obj = stripe_payment_object return unless obj pi_status = Payment::Apis::Stripe.payment_intent?() ? obj.status : nil case pi_status when 'canceled' logger.info("Payment #{id}: PI #{stripe_payment_intent_id} canceled on Stripe, voiding") reason = obj.cancellation_reason == 'expired' ? 'Authorization expired on Stripe' : 'PI canceled on Stripe' transactions.create!( amount: 0, action: 'void', success: true, reference: stripe_payment_intent_id || , message: "AUTO-VOID: #{reason} (status: canceled, reason: #{obj.cancellation_reason})", test: false ) payment_voided! when 'succeeded' # PI was finalized — either captured or released externally sync_external_capture(obj) when 'requires_capture' # PI still open — check if approaching reauth deadline if stripe_resolver..reauth_needed Payment::Gateways::CreditCard.new(self). end else # Legacy charge objects or unknown status — fall back to old behavior if obj.try(:captured?) transaction = OrderTransaction.new( amount: obj.try(:amount_captured).to_f, action: 'capture', success: true, reference: , message: "THIS PAYMENT WAS MANUALLY CAPTURED ON THE PAYMENT PLATFORM", params: obj.to_hash, test: false ) transactions.push(transaction) payment_captured! elsif stripe_resolver..reauth_needed Payment::Gateways::CreditCard.new(self). end end end |
#check_paypal_invoice_payment_status ⇒ Boolean?
Check PayPal for whether a hosted invoice has been paid yet; if so
capture locally and (when every PayPal-invoice payment on the order
is captured) release the order.
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 |
# File 'app/models/payment.rb', line 818 def check_paypal_invoice_payment_status if response = Payment::Apis::Paypal.get_invoice_details() res = JSON.parse(response.to_s) invoice_status = res['status'] if invoice_status.present? && invoice_status == 'PAID' Payment::Gateways::PaypalInvoice.new(self).capture(res) order.reload order.release_order if order.payments.paypal_invoices.all?(&:captured?) true else # Paypal invoice has not been paid yet false end elsif captured? # If the payment is captured do nothing. We could verify that the receipt has been created, etc. true end end |
#check_paypal_payment_status ⇒ Payment::PaypalStatusResult
Re-poll PayPal for the current state of this authorization and
reconcile the local payment: capture externally, void, expire,
decline, or attempt a reauthorization, depending on what PayPal
reports. Used by the daily PayPal sync worker.
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 |
# File 'app/models/payment.rb', line 564 def check_paypal_payment_status return PaypalStatusResult.new(status: :not_authorized, message: 'Payment is not in authorized state') unless response = Payment::Apis::Paypal.() auth_status = response['status'] auth_expiration = response['expiration_time']&.to_time (auth_expiration) case auth_status when 'CAPTURED' sync_paypal_external_capture(response) PaypalStatusResult.new(status: :captured, message: 'Payment was captured externally on PayPal') when 'VOIDED' logger.info("Payment #{id}: PayPal authorization voided externally") transactions.create!( amount: 0, action: 'void', success: true, reference: , message: "AUTO-VOID: PayPal authorization voided externally", test: false ) payment_voided! PaypalStatusResult.new(status: :voided, message: 'Authorization was voided on PayPal') when 'EXPIRED' logger.info("Payment #{id}: PayPal authorization expired") transactions.create!( amount: 0, action: 'void', success: true, reference: , message: "AUTO-VOID: PayPal authorization expired", test: false ) payment_expired! PaypalStatusResult.new(status: :expired, message: 'Authorization has expired') when 'DENIED' logger.info("Payment #{id}: PayPal authorization denied") transaction_declined! PaypalStatusResult.new(status: :denied, message: 'Authorization was denied') when 'CREATED', 'PENDING' (auth_expiration) else PaypalStatusResult.new(status: :unknown, message: "Unknown PayPal status: #{auth_status}") end end |
#communication_resource ⇒ Order
Resource the audit/comm subsystem keys off — for payments, that's
the parent Order.
323 |
# File 'app/models/payment.rb', line 323 belongs_to :order, optional: true |
#credit_card_vault ⇒ CreditCardVault
164 |
# File 'app/models/payment.rb', line 164 belongs_to :credit_card_vault, primary_key: 'vault_id', foreign_key: 'vault_id', optional: true |
#credit_memo ⇒ CreditMemo
165 |
# File 'app/models/payment.rb', line 165 belongs_to :credit_memo, optional: true |
#crm_link ⇒ String
CRM order URL for this payment, swallowing routing errors so audit
exports never blow up on a bad route.
1003 1004 1005 1006 1007 |
# File 'app/models/payment.rb', line 1003 def crm_link UrlHelper.instance.order_path(order) rescue StandardError '' end |
#currency_symbol ⇒ String
Returns currency symbol used in CRM/invoice displays.
1176 1177 1178 |
# File 'app/models/payment.rb', line 1176 def currency_symbol Money::Currency.new(currency).symbol end |
#customer ⇒ Customer
161 |
# File 'app/models/payment.rb', line 161 belongs_to :customer, optional: true |
#deep_dup ⇒ Payment
Clone the payment, including transactions/uploads via deep_clone,
but drop the Delivery association so the copy can be re-attached
on the target shipment without violating the unique pairing.
317 318 319 |
# File 'app/models/payment.rb', line 317 def deep_dup deep_clone(except: :delivery_id) end |
#default_cc_options(ip_address = nil, email = nil) ⇒ Hash
ActiveMerchant-style options hash used when authorizing/capturing
against the card gateway from an order context. Includes order
metadata and the Stripe customer id when charging a stored vault
card.
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 |
# File 'app/models/payment.rb', line 1337 def (ip_address = nil, email = nil) = { description: (order.reference_number.present? ? "Order #{order.reference_number}" : "Order ID #{order.id}"), statement_description: "WarmlyYours #{order.reference_number}", currency:, shipping_address: order.shipping_address.format_for_payment_gateway(true), metadata: { email:, ip: ip_address, order_id: order.id, payment_id: id } } # only pass the customer as an option if we detect a vault_id, otherwise is will just charge the first card stored on the customer account [:customer] = order.customer.stripe_customer_id if vault_id.present? end |
#default_echeck_options(_ip_address = nil) ⇒ Hash
Forte-formatted options hash used when authorizing an eCheck.
_ip_address is retained for signature compatibility with
#default_cc_options.
1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 |
# File 'app/models/payment.rb', line 1369 def (_ip_address = nil) { # customer: order.customer_id, # ip: ip_address, # email: self.authorization.email, # shipping_address: order.shipping_address.format_for_payment_gateway(true), billing_address: order.billing_address.format_for_forte(true), order_id: order.reference_number # description: "Order #{order.reference_number}" } end |
#default_paypal_options ⇒ Hash
Minimal options hash for PayPal API calls (just currency for now).
1358 1359 1360 1361 1362 |
# File 'app/models/payment.rb', line 1358 def { currency: currency } end |
#delivery ⇒ Delivery
167 |
# File 'app/models/payment.rb', line 167 belongs_to :delivery, inverse_of: :payments, optional: true |
#detect_fraud(force_new_report: false) ⇒ Order::FraudDetector::Result?
Run the fraud detector for credit card / PayPal / eCheck payments.
Skipped for non-eligible categories and SmartService orders.
Errors are reported to AppSignal but never raised, so authorize
transitions don't fail because the fraud report did.
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 |
# File 'app/models/payment.rb', line 1016 def detect_fraud(force_new_report: false) return nil if order.nil? return nil unless category.in?(['Credit Card', 'PayPal', 'eCheck']) return nil if order.belongs_to_smartservice_group? begin Order::FraudDetector.new(order, self).process(force_new_report:) rescue StandardError => e ErrorReporting.error(e, "FraudDetector Error - Unable to process new FraudDetector request for Payment ID:#{id}") nil end end |
#does_not_allow_capture? ⇒ Boolean
981 982 983 |
# File 'app/models/payment.rb', line 981 def does_not_allow_capture? category.in?(CATEGORIES_NOT_ALLOWING_CAPTURE) end |
#email_collection_for_select ⇒ Array<String>
Email options for the "send receipt to" select on the payment form:
every email on the customer plus the address captured on the
payment itself, deduped.
1062 1063 1064 1065 1066 |
# File 'app/models/payment.rb', line 1062 def email_collection_for_select customer_for_collection = customer customer_for_collection ||= order&.customer [customer_for_collection&.all_emails, email].flatten.compact.uniq end |
#full_card_number ⇒ String?
Reconstruct a masked PAN for display: BIN + xxx + last4 if both are
known, otherwise just the last 4 with a generic mask.
1072 1073 1074 1075 1076 1077 1078 |
# File 'app/models/payment.rb', line 1072 def full_card_number if issuer_number and last4 "#{issuer_number}xxxxxx#{last4}" elsif last4 "xxxxxxxxxxxx#{last4}" end end |
#full_name ⇒ String
Returns cardholder/payer full name from the captured
first_name/last_name pair.
1182 1183 1184 |
# File 'app/models/payment.rb', line 1182 def full_name [first_name, last_name].compact.join(' ') end |
#full_name=(value) ⇒ Object
Split a free-form name string via PersonNameParser and assign
first_name/last_name accordingly.
1190 1191 1192 1193 1194 |
# File 'app/models/payment.rb', line 1190 def full_name=(value) pnp = PersonNameParser.new(value) self.first_name = pnp.first self.last_name = pnp.last end |
#funds_fully_refunded? ⇒ Boolean
1049 1050 1051 |
# File 'app/models/payment.rb', line 1049 def funds_fully_refunded? total_refunded == total_captured end |
#funds_partially_refunded? ⇒ Boolean
1053 1054 1055 |
# File 'app/models/payment.rb', line 1053 def funds_partially_refunded? total_refunded.positive? and total_refunded < total_captured end |
#gateway_class ⇒ Class
Strategy class used to authorize/capture/void this payment. Resolves
category to a constant under Gateways; falls back to
Payment::Gateways::Default when unmatched.
355 356 357 358 359 360 |
# File 'app/models/payment.rb', line 355 def gateway_class class_name = category.parameterize(separator: '_') # Map non-conventional class names class_name = { VPO: 'VerbalPurchaseOrder', PO: 'PurchaseOrder' }[class_name] || class_name "Payment::Gateways::#{class_name.classify}".safe_constantize || Payment::Gateways::Default end |
#is_advanced_replacement? ⇒ Boolean
909 910 911 |
# File 'app/models/payment.rb', line 909 def is_advanced_replacement? category == ADV_REPL end |
#is_amazon_pay? ⇒ Boolean
921 922 923 |
# File 'app/models/payment.rb', line 921 def is_amazon_pay? category == AMAZON_PAY end |
#is_crm_legacy_vault? ⇒ Boolean
1325 1326 1327 |
# File 'app/models/payment.rb', line 1325 def is_crm_legacy_vault? order&.order_reception_type == 'CRM' && (vault_id && credit_card_vault&.address_line1.blank?) end |
#is_plaid? ⇒ Boolean
917 918 919 |
# File 'app/models/payment.rb', line 917 def is_plaid? category == PLAID end |
#is_po? ⇒ Boolean
901 902 903 |
# File 'app/models/payment.rb', line 901 def is_po? category.in?([PO, VPO]) end |
#is_receipt_skippable? ⇒ Boolean
1321 1322 1323 |
# File 'app/models/payment.rb', line 1321 def is_receipt_skippable? .in?(%w[paypal_invoice check]) and state == 'captured' and invoice.nil? end |
#is_rma_credit? ⇒ Boolean
905 906 907 |
# File 'app/models/payment.rb', line 905 def is_rma_credit? category == RMA_CREDIT end |
#is_store_credit? ⇒ Boolean
913 914 915 |
# File 'app/models/payment.rb', line 913 def is_store_credit? category == STORE_CREDIT end |
#is_www_apple_pay? ⇒ Boolean
1286 1287 1288 |
# File 'app/models/payment.rb', line 1286 def is_www_apple_pay? order&.order_reception_type == 'Online' && transactions.any? { |t| t.params&.dig('source')&.dig('tokenization_method') == 'apple_pay' } end |
#last_authorization_message ⇒ String?
Returns message text on the most recent
OrderTransaction (typically the gateway's last response message).
987 988 989 |
# File 'app/models/payment.rb', line 987 def transactions&.first&. end |
#legacy_order ⇒ Order
162 |
# File 'app/models/payment.rb', line 162 belongs_to :legacy_order, class_name: 'Order', foreign_key: 'order_id', optional: true |
#paypal_over_capture_headroom ⇒ BigDecimal
How much more is still capturable on this PayPal auth before
hitting #paypal_over_capture_limit, considering every active
sibling payment.
475 476 477 478 479 480 481 482 483 |
# File 'app/models/payment.rb', line 475 def paypal_over_capture_headroom limit = paypal_over_capture_limit return BigDecimal('0') unless limit all_committed = Payment.where(authorization_code: , authorization_type: 'paypal') .where.not(state: %w[voided declined expired]) .sum(:amount) [limit - all_committed, BigDecimal('0')].max end |
#paypal_over_capture_limit ⇒ BigDecimal?
Maximum total that can be captured against this PayPal auth: the
smaller of 115% of the original total and original + $75. Returns
nil when the original total is unknown.
463 464 465 466 467 468 |
# File 'app/models/payment.rb', line 463 def paypal_over_capture_limit auth_total = paypal_shared_auth_total return nil unless auth_total&.positive? [auth_total * PAYPAL_OVER_CAPTURE_PERCENT, auth_total + PAYPAL_OVER_CAPTURE_MAX_INCREASE].min end |
#paypal_shared_auth_total ⇒ BigDecimal?
Original total at the time the PayPal authorization was first
established (sum of every sibling's amount). Stored on
paypal_metadata because PayPal will not let you reauthorize for
more than 115% of the original total.
452 453 454 455 456 |
# File 'app/models/payment.rb', line 452 def paypal_shared_auth_total BigDecimal(&.dig('shared_authorization_total').to_s) rescue ArgumentError nil end |
#pending_release_authorization? ⇒ Boolean
925 926 927 |
# File 'app/models/payment.rb', line 925 def !payment_approved? && [:required] == true end |
#po_upload ⇒ Upload?
First non-deleted upload tagged purchase_order — the customer's
PO PDF supporting a PO payment.
995 996 997 |
# File 'app/models/payment.rb', line 995 def po_upload uploads.in_category('purchase_order').valid.first end |
#process_tx_results(tx) ⇒ Object
Copy card metadata, billing/shipping addresses, and Stripe Radar
signals from a successful Stripe OrderTransaction (or the linked
CreditCardVault) onto this payment so the CRM can show full
context without re-querying Stripe.
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 |
# File 'app/models/payment.rb', line 1202 def process_tx_results(tx) if vault_id.present? and vault = CreditCardVault.find_by(vault_id:) self.issuer_number = vault.issuer_number self.card_type = vault.card_type self.name = vault.name self.exp_month = vault.exp_month self.exp_year = vault.exp_year self.reference = self.last4 = vault.number self.address_line1_check = vault.address_line1_check self.address_zip_check = vault.address_zip_check self.cvc_check = vault.cvc_check self.card_identifier = vault.vault_id self.card_country = nil self.billing_address_line1 = vault.address_line1 self.billing_address_line2 = vault.address_line2 self.billing_address_city = vault.address_city self.billing_address_state = vault.address_state self.billing_address_zip = vault.zip_code self.billing_address_country = vault.address_country end if (shipping_atts = order&.ship_to_attributes).present? shipping = shipping_atts[:address] self.shipping_address_name = shipping_atts[:attention_name] unless shipping_atts[:attention_name].blank? || shipping.is_placeholder unless shipping_atts[:name].blank? || shipping.is_placeholder || shipping_atts[:attention_name] == shipping_atts[:name] self.shipping_address_name = shipping_atts[:name] end self.shipping_address_line1 = shipping.street1 self.shipping_address_line2 = shipping.street2 self.shipping_address_city = shipping.city self.shipping_address_state = shipping.state&.name self.shipping_address_zip = shipping.zip self.shipping_address_country = shipping.country&.iso end if tx_source = tx.params['source'] self.card_type = tx_source['brand'] self.name = tx_source['name'] self.exp_month = tx_source['exp_month'] self.exp_year = tx_source['exp_year'] self.reference = "....#{tx_source['last4']}" self.last4 = tx_source['last4'] self.address_line1_check = tx_source['address_line1_check'] self.address_zip_check = tx_source['address_zip_check'] self.cvc_check = tx_source['cvc_check'] self.card_identifier = tx_source['id'] self.card_country = tx_source['country'] self.billing_address_line1 = tx_source['address_line1'] self.billing_address_line2 = tx_source['address_line2'] self.billing_address_city = tx_source['address_city'] self.billing_address_state = tx_source['address_state'] self.billing_address_zip = tx_source['address_zip'] self.billing_address_country = tx_source['address_country'] end # Removing this after disconnecting David's active merchant branch on July 2020 # if tx.params["shipping"] # self.shipping_address_name = tx.params["shipping"]["name"] # if tx_shipping = tx.params["shipping"]["address"] # self.shipping_address_line1 = tx_shipping["line1"] # self.shipping_address_line2 = tx_shipping["line2"] # self.shipping_address_city = tx_shipping["city"] # self.shipping_address_state = tx_shipping["state"] # self.shipping_address_zip = tx_shipping["postal_code"] # self.shipping_address_country = tx_shipping["country"] # end # end if tx_outcome = tx.params['outcome'] self.radar_network_status = tx_outcome['network_status'] self.radar_reason = tx_outcome['reason'] self.radar_risk_level = tx_outcome['risk_level'] self. = tx_outcome['seller_message'] self.radar_type = tx_outcome['type'] end return unless livemode = tx.params['livemode'] self.test = livemode == false end |
#receipts ⇒ ActiveRecord::Relation<Receipt>
174 |
# File 'app/models/payment.rb', line 174 has_many :receipts |
#refunded_on_stripe? ⇒ Boolean
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 |
# File 'app/models/payment.rb', line 1096 def refunded_on_stripe? return false if != 'credit_card' obj = stripe_payment_object return false unless obj if Payment::Apis::Stripe.payment_intent?() obj.status == 'canceled' else obj.try(:refunded?) rescue false end end |
#resend_paypal_invoice ⇒ Hash{Symbol=>Object}
Re-send the PayPal invoice reminder email via the PayPal API.
842 843 844 845 846 847 848 849 |
# File 'app/models/payment.rb', line 842 def resend_paypal_invoice response = Payment::Apis::Paypal.remind_invoice() if response['_http_success'] { success: true } else { success: false, message: 'Something went wrong with Paypal reminder.' } end end |
#send_authorization_email_notification ⇒ Communication, String
Send the cardholder a "we authorized your card" email. Only fires
for CREDIT_CARD payments where send_authorization_email was opted
into. Returns a string when the message wasn't sent so the caller
can surface the reason.
935 936 937 938 939 940 941 942 943 944 945 946 947 948 |
# File 'app/models/payment.rb', line 935 def if category == CREDIT_CARD and email.present? and == true sender = order.customer.try(:primary_sales_rep) CommunicationBuilder.new( resource: self, sender_party: sender, sender: (sender.nil? ? INFO_EMAIL : nil), emails: email, bcc: (sender.nil? ? nil : sender.email) ).create else 'Unable to send authorization email' end end |
#send_wire_info_email ⇒ Communication, String
Send the customer the WIRE_TRANSFER_INFO email with bank details
they need to wire funds. Only fires for WIRE payments with an email
captured. Falls back to a string when the message can't be sent.
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 |
# File 'app/models/payment.rb', line 955 def send_wire_info_email if category == WIRE and email.present? sender = order.customer.try(:primary_sales_rep) comm = CommunicationBuilder.new( resource: order, sender_party: sender, sender: (sender.nil? ? INFO_EMAIL : nil), emails: email, merge_options: { order_reference: order.cart_identifier, order_total: amount, country_instructions: (order.catalog.id == 1 ? 'usa' : 'ca') }, bcc: (sender.nil? ? nil : sender.email), template: EmailTemplate.find_by(system_code: 'WIRE_TRANSFER_INFO') ).create else 'Unable to send wire info email' end end |
#shared_paypal_auth? ⇒ Boolean
437 438 439 440 441 |
# File 'app/models/payment.rb', line 437 def shared_paypal_auth? == 'paypal' && .present? && Payment.where(authorization_code: , authorization_type: 'paypal') .where.not(id: id).exists? end |
#shared_paypal_auth_siblings ⇒ ActiveRecord::Relation<Payment>
Other authorized PayPal payments sharing this PayPal authorization
id (PayPal supports up to 115% over-capture across siblings).
429 430 431 432 433 434 435 |
# File 'app/models/payment.rb', line 429 def shared_paypal_auth_siblings return Payment.none unless == 'paypal' && .present? Payment.where(authorization_code: , authorization_type: 'paypal') .where.not(id: id) .where(state: 'authorized') end |
#shared_pi? ⇒ Boolean
420 421 422 423 |
# File 'app/models/payment.rb', line 420 def shared_pi? stripe_payment_intent_id.present? && Payment.where(stripe_payment_intent_id: stripe_payment_intent_id).where.not(id: id).exists? end |
#shared_pi_siblings ⇒ ActiveRecord::Relation<Payment>
Other authorized payments that share this Stripe payment intent
(split orders / partial captures stack on one PI). Excludes self.
401 402 403 404 405 406 407 |
# File 'app/models/payment.rb', line 401 def shared_pi_siblings return Payment.none unless stripe_payment_intent_id.present? Payment.where(stripe_payment_intent_id: stripe_payment_intent_id) .where.not(id: id) .where(state: %w[authorized]) end |
#shipping_address ⇒ Address
Build a transient Address from the captured shipping fields. Used
to compare against the order's ship-to for fraud signals.
1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 |
# File 'app/models/payment.rb', line 1428 def shipping_address Address.new( street1: shipping_address_line1, street2: shipping_address_line2, city: shipping_address_city, zip: shipping_address_zip, state_code: State.code_for_string(shipping_address_state), country_iso3: Country.iso3_for_string(shipping_address_country) ) end |
#stripe_api_key ⇒ String
Stripe API key appropriate for this payment's currency (the codebase
routes USD through one Stripe account, CAD through another).
1282 1283 1284 |
# File 'app/models/payment.rb', line 1282 def stripe_api_key Payment::Apis::Stripe.api_key(currency) end |
#stripe_payment_object ⇒ Stripe::PaymentIntent, ... Also known as: stripe_charge
Fetch the upstream Stripe object (Charge or PaymentIntent) for this
payment. Returns false on missing auth or any Stripe API error so
callers can guard with a single unless.
1085 1086 1087 1088 1089 1090 1091 |
# File 'app/models/payment.rb', line 1085 def stripe_payment_object return false if .blank? Payment::Apis::Stripe.retrieve_payment_object(, currency: currency) rescue ::Stripe::StripeError false end |
#stripe_resolver ⇒ Payment::StrategyResolver
Memoised Stripe capture/reauth strategy resolver — encapsulates the
capture window math (Stripe legacy 7d, extended 30d, PayPal 29d).
366 367 368 |
# File 'app/models/payment.rb', line 366 def stripe_resolver @stripe_resolver ||= Payment::StrategyResolver.new(self) end |
#supports_extended_authorization? ⇒ Boolean
380 381 382 |
# File 'app/models/payment.rb', line 380 def stripe_resolver. end |
#supports_incremental_authorization? ⇒ Boolean
376 377 378 |
# File 'app/models/payment.rb', line 376 def stripe_resolver. end |
#supports_multicapture? ⇒ Boolean
370 371 372 373 374 |
# File 'app/models/payment.rb', line 370 def supports_multicapture? return true if == 'paypal' stripe_resolver.supports_multicapture? end |
#sync_capture_before_from_paypal_expiry(auth_expiration) ⇒ Object
Push capture_before forward when PayPal hands back a later
expiration_time than we have stored — this keeps the local
capture-window cache in sync with PayPal's truth.
701 702 703 704 705 706 |
# File 'app/models/payment.rb', line 701 def sync_capture_before_from_paypal_expiry(auth_expiration) effective_capture_before = auth_expiration - PAYPAL_HONOR_PERIOD if capture_before.nil? || capture_before < effective_capture_before update_column(:capture_before, effective_capture_before) end end |
#sync_external_capture(pi) ⇒ Object
Reflect a Stripe-side capture (or release) of a Payment Intent into
this Payment. Computes the share that hasn't been claimed by
sibling payments yet, mints a synthetic capture transaction for it,
and advances state — or voids the payment when the PI finalised
without funds for us.
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 |
# File 'app/models/payment.rb', line 774 def sync_external_capture(pi) pi_received_cents = pi.amount_received.to_i sibling_captured_cents = all_pi_siblings .where.not(state: %w[declined expired]) .sum { |s| (s.total_captured * 100).to_i } unaccounted_cents = pi_received_cents - sibling_captured_cents - (total_captured * 100).to_i if unaccounted_cents.positive? capture_amount_cents = [unaccounted_cents, (amount * 100).to_i].min logger.info("Payment #{id}: PI finalized on Stripe, syncing external capture of #{capture_amount_cents} cents") transactions.create!( amount: capture_amount_cents, action: 'capture', success: true, reference: , message: "THIS PAYMENT WAS CAPTURED EXTERNALLY ON STRIPE", params: pi.to_hash, test: false ) payment_captured! else # PI finalized but no funds captured for this payment — remaining auth was released logger.info("Payment #{id}: PI finalized on Stripe with no capture for this payment, voiding") msg = total_captured.positive? ? "AUTO-VOID: PI finalized, remaining authorization released (partial capture existed)" : "AUTO-VOID: PI finalized with no capture for this payment, authorization released" transactions.create!( amount: 0, action: 'void', success: true, reference: stripe_payment_intent_id || , message: msg, test: false ) payment_voided! end end |
#sync_paypal_authorization_expiry(auth_expiration) ⇒ Object
Persist PayPal's authoritative authorization expiry into
paypal_metadata['authorization_expiry'] so callers don't have to
re-poll on every action.
617 618 619 620 621 622 623 |
# File 'app/models/payment.rb', line 617 def (auth_expiration) return unless auth_expiration.present? = || {} ['authorization_expiry'] = auth_expiration.iso8601 update_column(:paypal_metadata, ) end |
#sync_paypal_external_capture(auth_response) ⇒ Object
Record a capture that happened on the PayPal dashboard rather than
through Heatwave: append a synthetic capture OrderTransaction and
advance the state to captured.
630 631 632 633 634 635 636 637 638 639 640 641 642 643 |
# File 'app/models/payment.rb', line 630 def sync_paypal_external_capture(auth_response) captured_amount_dollars = auth_response.dig("amount", "value").to_f transaction = OrderTransaction.new( amount: (captured_amount_dollars * 100).to_i, action: 'capture', success: true, reference: auth_response['id'] || , message: "THIS PAYMENT WAS MANUALLY CAPTURED ON THE PAYMENT PLATFORM", params: auth_response.except('_http_status', '_http_success', '_raw_body'), test: false ) transactions.push(transaction) payment_captured! end |
#to_s ⇒ String
Returns "Payment 1234 (ORD-555)".
977 978 979 |
# File 'app/models/payment.rb', line 977 def to_s "Payment #{id} (#{order&.reference_number})" end |
#total_authorized ⇒ Float
Live authorization total in dollars. Prefers the latest
incremental_authorization transaction (Stripe replaces the prior
auth amount on each step-up); otherwise sums the original auths.
1295 1296 1297 1298 1299 1300 1301 1302 |
# File 'app/models/payment.rb', line 1295 def latest_increment = transactions.select { |t| t.success && t.action == 'incremental_authorization' }.max_by(&:created_at) if latest_increment (latest_increment.amount.to_f / 100).round(2) else transactions.select { |t| t.success && t.action.in?(%w[authorization authorize]) }.sum { |t| t.amount.to_f / 100 }.to_f.round(2) end end |
#total_captured ⇒ Float
Sum of all successful capture/purchase/settle transactions in
dollars (gateway records cents).
1308 1309 1310 1311 1312 |
# File 'app/models/payment.rb', line 1308 def total_captured transactions.select do |t| t.success and (t.action == 'capture' or t.action == 'purchase' or t.action == 'settle') end.sum { |t| t.amount.to_f / 100 }.to_f.round(2) end |
#total_refunded ⇒ Float
Sum of all successful refund transactions in dollars.
1317 1318 1319 |
# File 'app/models/payment.rb', line 1317 def total_refunded transactions.select { |t| t.success and t.action == 'refund' }.sum { |t| t.amount.to_f / 100 }.to_f.round(2) end |
#transaction_reference(action) ⇒ String?
Reference returned by the gateway for the first successful
transaction of action (e.g. authorization, capture, refund).
1034 1035 1036 1037 1038 |
# File 'app/models/payment.rb', line 1034 def transaction_reference(action) return unless payment = transactions.where(action:).where(success: true).order(:id).first payment.reference end |
#transactions ⇒ ActiveRecord::Relation<OrderTransaction>
172 |
# File 'app/models/payment.rb', line 172 has_many :transactions, class_name: 'OrderTransaction', dependent: :destroy |
#update_authorization_code(action) ⇒ Object
Refresh authorization_code from the latest successful
transaction of the given action (e.g. 'authorization',
'reauthorization').
1404 1405 1406 1407 |
# File 'app/models/payment.rb', line 1404 def (action) update(authorization_code: transaction_reference(action)) payment.update(amount:) end |
#uploads ⇒ ActiveRecord::Relation<Upload>
173 |
# File 'app/models/payment.rb', line 173 has_many :uploads, as: :resource, dependent: :destroy |
#vpo_contact ⇒ Contact
168 |
# File 'app/models/payment.rb', line 168 belongs_to :vpo_contact, class_name: 'Contact', optional: true |