mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
dispute.state.updated and dispute.state.changed both normalize to dispute.updated and both are clawback events. A dispute report carries the dispute's version and updated_at, and dispute.Report (embedded in the invoice and in the wallet payment's record, models/paymentorg) keeps the latest: a report older by version, else by updated_at, else by phase (inquiry, chargeback, decided), changes nothing, and WON stays final. An inquiry holds no funds: it takes nothing from a wallet, and on an invoice it is recorded while the period stays paid. A chargeback (EVIDENCE_REQUIRED, PROCESSING), or one lost or accepted, takes a wallet payment back once and unpays a period; a chargeback won gives the wallet payment back and restores the period. Co-authored-by: Hanzo Dev <[email protected]>
87 lines
2.8 KiB
Go
87 lines
2.8 KiB
Go
package dispute
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// StateWon is the processor's dispute state in which the merchant kept the
|
|
// payment: the withheld funds were returned.
|
|
const StateWon = "WON"
|
|
|
|
// Withholds reports whether the processor withholds a disputed payment's funds in
|
|
// state: a chargeback (EVIDENCE_REQUIRED, PROCESSING), or one lost or accepted. An
|
|
// inquiry (INQUIRY_EVIDENCE_REQUIRED, INQUIRY_PROCESSING, INQUIRY_CLOSED) holds no
|
|
// funds, and a dispute won has had them returned.
|
|
func Withholds(state string) bool {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case "EVIDENCE_REQUIRED", "PROCESSING", "LOST", "ACCEPTED":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// phase is how far along a dispute in state is: an inquiry, a chargeback, or
|
|
// decided. A dispute only moves forward through them, so a report from an earlier
|
|
// phase than the one held is the older of the two.
|
|
func phase(state string) int {
|
|
switch state {
|
|
case "INQUIRY_EVIDENCE_REQUIRED", "INQUIRY_PROCESSING", "INQUIRY_CLOSED":
|
|
return 1
|
|
case "EVIDENCE_REQUIRED", "PROCESSING":
|
|
return 2
|
|
case StateWon, "LOST", "ACCEPTED":
|
|
return 3
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Report is the latest state a processor reported for a dispute of one payment,
|
|
// with the dispute's version and updated_at that order reports against each other.
|
|
type Report struct {
|
|
Dispute string `json:"dispute,omitempty"`
|
|
DisputeVersion int64 `json:"disputeVersion,omitempty"`
|
|
DisputeAt time.Time `json:"disputeAt,omitempty"`
|
|
}
|
|
|
|
// older reports whether a report of state at version and updated_at at is older
|
|
// than the held one: by the dispute's version when both carry one, else by its
|
|
// updated_at when both carry one, else by the dispute's phase.
|
|
func (r *Report) older(state string, version int64, at time.Time) bool {
|
|
switch {
|
|
case version > 0 && r.DisputeVersion > 0:
|
|
return version < r.DisputeVersion
|
|
case !at.IsZero() && !r.DisputeAt.IsZero():
|
|
return at.Before(r.DisputeAt)
|
|
}
|
|
return phase(state) > 0 && phase(state) < phase(r.Dispute)
|
|
}
|
|
|
|
// SetDispute records a reported state, in upper case, and reports whether it is
|
|
// the latest known: a report older than the one held, or one other than WON after
|
|
// a dispute was won, changes nothing and answers false. The same report again
|
|
// answers true. A version or updated_at the report does not carry keeps the held
|
|
// one.
|
|
func (r *Report) SetDispute(state string, version int64, at time.Time) bool {
|
|
state = strings.ToUpper(strings.TrimSpace(state))
|
|
if r.older(state, version, at) {
|
|
return false
|
|
}
|
|
if r.Dispute == StateWon && state != StateWon {
|
|
return false
|
|
}
|
|
r.Dispute = state
|
|
if version > 0 {
|
|
r.DisputeVersion = version
|
|
}
|
|
if !at.IsZero() {
|
|
r.DisputeAt = at
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Disputed reports whether the reported dispute withholds the payment's funds.
|
|
func (r Report) Disputed() bool {
|
|
return Withholds(r.Dispute)
|
|
}
|