package yookassa import ( "encoding/json" "fmt" ) // Notification events this integration subscribes to. An event name is ".": the // object whose status changed and the status it entered. const ( // EventPaymentSucceeded means the money was taken and the order may be credited. EventPaymentSucceeded = "payment.succeeded" // EventPaymentCanceled means the payment was actively declined or abandoned; nothing is credited // and the payer is told the attempt failed. EventPaymentCanceled = "payment.canceled" // EventRefundSucceeded reports a completed refund. It matters because the merchant cabinet is a // second way to issue one: a refund made there never passes through our API, so without this // event the money would go back while the chips stayed credited. EventRefundSucceeded = "refund.succeeded" ) // notificationType is the fixed value of a notification envelope's type field. const notificationType = "notification" // Notification is an incoming webhook envelope. Object is left raw because its shape depends on the // event — a payment for payment.*, a refund for refund.* — and because nothing in it may be acted on // before GetPayment confirms it: YooKassa does not sign notifications. type Notification struct { Type string `json:"type"` Event string `json:"event"` Object json.RawMessage `json:"object"` } // ParseNotification decodes a webhook body and checks the envelope is a notification with an event. // It deliberately validates nothing else: the body is untrusted input whose only job is to name an // object to re-read from the API. func ParseNotification(body []byte) (Notification, error) { var n Notification if err := json.Unmarshal(body, &n); err != nil { return Notification{}, fmt.Errorf("yookassa: decode notification: %w", err) } if n.Type != notificationType || n.Event == "" { return Notification{}, fmt.Errorf("yookassa: not a notification envelope (type %q, event %q)", n.Type, n.Event) } return n, nil } // Payment decodes the notification's object as a payment. Use it only for payment.* events; it // yields the payment id to re-read, never the payment state to act on. func (n Notification) Payment() (Payment, error) { var p Payment if err := json.Unmarshal(n.Object, &p); err != nil { return Payment{}, fmt.Errorf("yookassa: decode notification payment: %w", err) } if p.ID == "" { return Payment{}, fmt.Errorf("yookassa: notification payment has no id") } return p, nil } // Refund decodes the notification's object as a refund. Use it only for refund.* events; it yields // the refund id to re-read, never the refund state to act on. func (n Notification) Refund() (Refund, error) { var r Refund if err := json.Unmarshal(n.Object, &r); err != nil { return Refund{}, fmt.Errorf("yookassa: decode notification refund: %w", err) } if r.ID == "" { return Refund{}, fmt.Errorf("yookassa: notification refund has no id") } return r, nil }