mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans, usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0. Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
54 lines
1.9 KiB
Go
54 lines
1.9 KiB
Go
// Reference: https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/
|
|
package ipn
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/commerce/models/types/currency"
|
|
)
|
|
|
|
type IpnMessage struct {
|
|
Status string // transaction[0].status
|
|
PayerEmail string // sender_email
|
|
PayeeEmail string // transaction[0].receiver
|
|
Amount currency.Cents // extracted from transaction[0].amount
|
|
PayKey string // pay_key
|
|
Currency currency.Type
|
|
}
|
|
|
|
// NewIpnMessage parses a PayPal IPN form.
|
|
//
|
|
// It returns an error rather than a zero-amount message: an IPN whose amount
|
|
// does not parse is a payment notification we cannot attribute, and recording
|
|
// it as 0 would post a free transaction that reports success. The amount field
|
|
// is "<CURRENCY> <decimal>", so a field with no space is malformed too — it
|
|
// used to index parts[1] unguarded and panic.
|
|
func NewIpnMessage(form url.Values) (*IpnMessage, error) {
|
|
message := new(IpnMessage)
|
|
message.Status = form.Get("transaction[0].status")
|
|
message.PayerEmail = form.Get("sender_email")
|
|
message.PayeeEmail = form.Get("transaction[0].receiver")
|
|
message.PayKey = form.Get("pay_key")
|
|
|
|
raw := form.Get("transaction[0].amount")
|
|
parts := strings.Fields(raw)
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("ipn: amount %q is not \"<CURRENCY> <decimal>\"", raw)
|
|
}
|
|
|
|
// The currency is read first because it decides the scale — it is in the
|
|
// same field as the digits, so there is nothing to guess. Parsing at a
|
|
// hardcoded USD scale read a zero-decimal notification a hundred times too
|
|
// large: "JPY 500" is 500 minor units, not 50000.
|
|
message.Currency = currency.Type(strings.ToLower(parts[0]))
|
|
amount, err := message.Currency.Parse(parts[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ipn: %w", err)
|
|
}
|
|
message.Amount = amount
|
|
|
|
return message, nil
|
|
}
|