payment

package
v1.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ErrPaymentDisabled          = "支付功能未启用"
	ErrInvalidAmount            = "金额必须大于 0 且最多 2 位小数"
	ErrPriceRequiresOneForEach  = "仅一码一用分发支持设置金额"
	ErrCreatorNotConfigured     = "项目创建者尚未配置支付凭据,无法发起支付"
	ErrPendingOrderExists       = "当前项目存在进行中或已完成订单,不可重复创建"
	ErrPaymentConfigNotFound    = "尚未配置支付凭据"
	ErrEncryptionKeyMissing     = "服务端未配置支付密钥加密密钥"
	ErrInvalidClientCredentials = "clientID 与 clientSecret 不能为空"
	ErrOrderNotFound            = "订单不存在"
	ErrOrderExpired             = "订单已过期"
	ErrCannotDeleteHasActive    = "存在未结束的付费项目,无法删除支付配置"
	ErrInvalidPriceDecimals     = "金额最多保留 2 位小数"
	ErrPriceTooLarge            = "金额超出允许范围"
)

Variables

View Source
var ErrOrderNotFoundSentinel = errors.New(ErrOrderNotFound)

ErrOrderNotFoundSentinel 外部判断

Functions

func BuildSign

func BuildSign(params map[string]string, secret string) string

BuildSign 按易支付/CodePay/VPay 兼容协议生成 MD5 签名(小写十六进制)。 规则:取非空参数,排除 sign 与 sign_type,按 key ASCII 升序,用 k1=v1&k2=v2 拼接, 末尾追加 secret,整体 MD5。

func CallbackURLs

func CallbackURLs() (notifyURL, returnURL string)

CallbackURLs 返回当前平台配置的回调地址,用于前端展示给用户。

func DecryptSecret

func DecryptSecret(encoded, key string) (string, error)

DecryptSecret 解密 EncryptSecret 的输出。

func DeletePaymentConfig

func DeletePaymentConfig(c *gin.Context)

DeletePaymentConfig DELETE /api/v1/users/payment-config

func DeleteUserPaymentConfig

func DeleteUserPaymentConfig(ctx context.Context, userID uint64) error

DeleteUserPaymentConfig 删除用户支付配置。 若该用户存在 Price>0 且未结束的自有项目则拒绝删除,避免后续领取者无法付款。

func DispatchReceive

func DispatchReceive(c *gin.Context)

DispatchReceive POST /api/v1/projects/:id/receive 运行在 project.ReceiveProjectMiddleware() 之后,已通过资格校验并在 context 注入 project。 付费项目:返回 {require_payment:true, pay_url, ...};前端直接跳转 pay_url。 免费项目:执行原领取事务,返回 {itemContent}。

func EncryptSecret

func EncryptSecret(plaintext, key string) (string, error)

EncryptSecret 使用 AES-256-GCM 加密明文,输出 base64(nonce|ciphertext|tag)。

func GetPaymentConfig

func GetPaymentConfig(c *gin.Context)

GetPaymentConfig GET /api/v1/users/payment-config @Summary 获取当前用户的支付配置(不返回明文 secret)

func HandleExpireStaleOrders

func HandleExpireStaleOrders(ctx context.Context, _ *asynq.Task) error

HandleExpireStaleOrders 清理长时间未付款的 PENDING 订单。 查询条件:status=PENDING 且 expire_at 超时超过 5 分钟。 额外 5 分钟宽限期确保 epay 的异步 notify 回调在此之前已到达, 避免"cleanup 先置 FAILED + RPush,notify 随后到达发现已无 PENDING 订单"的竞态。

func HandleNotify

func HandleNotify(ctx context.Context, q map[string]string) (bool, string)

HandleNotify 处理异步支付回调。 返回 (success bool, reason string):success=true 表示应返回文本 "success"; 否则返回 "fail",epay 最多重试 5 次,每次间隔由对方决定。 实现关键点:

  1. 拉起订单 → 验签(使用订单记录的 PayeeID 对应凭据) → 校验 trade_status/pid/money
  2. 幂等:若订单已是 COMPLETED/REFUNDED 直接 success
  3. CAS 推进 PENDING → PAID,成功者执行 fulfill 事务
  4. fulfill 失败 → 调 refund,成功后 RPush item 回 Redis,置 REFUNDED;本次返回 fail 让对方重试, 再次进入时因状态非 PENDING 直接 success

func HandleNotifyHTTP

func HandleNotifyHTTP(c *gin.Context)

HandleNotifyHTTP GET /api/v1/payment/notify 易支付兼容回调,返回纯文本 "success" / "fail"。

func SaveUserPaymentConfig

func SaveUserPaymentConfig(ctx context.Context, userID uint64, clientID, clientSecret string) error

SaveUserPaymentConfig 保存/更新用户的支付凭据,clientSecret 明文进入后会被加密。

func UpsertPaymentConfig

func UpsertPaymentConfig(c *gin.Context)

UpsertPaymentConfig PUT /api/v1/users/payment-config

func VerifySign

func VerifySign(params map[string]string, secret string) bool

VerifySign 校验签名。采用常量时间比较防止时序攻击。

Types

type GetPaymentConfigResponseData

type GetPaymentConfigResponseData struct {
	HasConfig         bool   `json:"has_config"`
	ClientID          string `json:"client_id"`
	SecretLast4       string `json:"secret_last4"`
	CallbackNotifyURL string `json:"callback_notify_url"`
	CallbackReturnURL string `json:"callback_return_url"`
	PaymentEnabled    bool   `json:"payment_enabled"`
}

GetPaymentConfigResponseData 当前用户支付配置的安全视图

type HandleNotifyParams

type HandleNotifyParams struct {
	Query map[string]string
}

HandleNotifyParams 易支付回调需要的全部字段

type OrderStatus

type OrderStatus int8

OrderStatus 订单状态机

PENDING(0)   -> PAID(1) -> COMPLETED(2)           // 正常路径
                        -> REFUNDING(3) -> REFUNDED(4)  // 发放失败
PENDING      -> FAILED(5)                         // 未付款超时 / 创建失败
const (
	OrderStatusPending   OrderStatus = 0
	OrderStatusPaid      OrderStatus = 1
	OrderStatusCompleted OrderStatus = 2
	OrderStatusRefunding OrderStatus = 3
	OrderStatusRefunded  OrderStatus = 4
	OrderStatusFailed    OrderStatus = 5
)

type PaymentInitiation

type PaymentInitiation struct {
	OutTradeNo string    `json:"out_trade_no"`
	PayURL     string    `json:"pay_url"`
	Amount     string    `json:"amount"`
	ExpireAt   time.Time `json:"expire_at"`
}

PaymentInitiation 返回给前端的发起支付信息

func InitiatePayment

func InitiatePayment(ctx context.Context, p *project.Project, payer *oauth.User, clientIP string) (*PaymentInitiation, error)

InitiatePayment 为付费项目的一次领取行为创建支付订单,并返回前端可直接跳转的支付 URL。 调用方已通过 ReceiveProjectMiddleware 的前置校验。 流程:载入商户凭据 → Redis LPop 预占 item → 持久化订单 PENDING → 构造 submit URL 返回。 若创建订单失败或拼接失败,需立即把 itemID RPush 回 Redis 以恢复库存。

type PaymentOrder

type PaymentOrder struct {
	ID            uint64          `gorm:"primaryKey;autoIncrement" json:"id"`
	OutTradeNo    string          `gorm:"size:64;uniqueIndex;not null" json:"out_trade_no"`
	TradeNo       string          `gorm:"size:64;index" json:"trade_no"`
	ProjectID     string          `gorm:"size:64;not null;index:idx_project_payer_status,priority:1" json:"project_id"`
	ItemID        uint64          `gorm:"index;not null" json:"item_id"`
	PayerID       uint64          `gorm:"not null;index:idx_project_payer_status,priority:2;index:idx_payer_status,priority:1" json:"payer_id"`
	PayeeID       uint64          `gorm:"index;not null" json:"payee_id"`
	PayeeClientID string          `gorm:"size:64" json:"payee_client_id"`
	Amount        decimal.Decimal `gorm:"type:decimal(10,2);not null" json:"amount"`
	Status        OrderStatus     `` /* 141-byte string literal not displayed */
	PaidAt        *time.Time      `json:"paid_at"`
	RefundedAt    *time.Time      `json:"refunded_at"`
	FailReason    string          `gorm:"size:255" json:"fail_reason"`
	ExpireAt      time.Time       `gorm:"index:idx_status_expire,priority:2" json:"expire_at"`
	ClientIP      string          `gorm:"size:64" json:"client_ip"`
	CreatedAt     time.Time       `gorm:"autoCreateTime" json:"created_at"`
	UpdatedAt     time.Time       `gorm:"autoUpdateTime" json:"updated_at"`
}

PaymentOrder 支付订单(一次付费领取 = 一个订单)

联合索引:

  • idx_project_payer_status (project_id, payer_id, status):查询某用户在某项目的待支付订单
  • idx_payer_status (payer_id, status):按用户快速查询待支付订单
  • idx_status_expire (status, expire_at):清理任务扫描超时 PENDING 订单

func (PaymentOrder) TableName

func (PaymentOrder) TableName() string

TableName 自定义表名

type ReceiveResponse

type ReceiveResponse struct {
	ItemContent    string `json:"itemContent,omitempty"`
	RequirePayment bool   `json:"require_payment,omitempty"`
	PayURL         string `json:"pay_url,omitempty"`
	OutTradeNo     string `json:"out_trade_no,omitempty"`
	Amount         string `json:"amount,omitempty"`
	ExpireAt       string `json:"expire_at,omitempty"`
}

ReceiveResponse 领取接口的统一响应:免费返回 itemContent;付费返回支付跳转信息。

type Response

type Response struct {
	ErrorMsg string      `json:"error_msg"`
	Data     interface{} `json:"data"`
}

Response 统一的 payment 响应壳,保持与其他 app 一致

type UpsertPaymentConfigRequest

type UpsertPaymentConfigRequest struct {
	ClientID     string `json:"client_id" binding:"required,min=1,max=64"`
	ClientSecret string `json:"client_secret" binding:"required,min=1,max=256"`
}

UpsertPaymentConfigRequest PUT 请求体

type UserPaymentConfig

type UserPaymentConfig struct {
	UserID          uint64    `gorm:"primaryKey" json:"user_id"`
	ClientID        string    `gorm:"size:64;not null" json:"client_id"`
	ClientSecretEnc string    `gorm:"size:512;not null" json:"-"`
	SecretLast4     string    `gorm:"size:8" json:"secret_last4"`
	CreatedAt       time.Time `gorm:"autoCreateTime" json:"created_at"`
	UpdatedAt       time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}

UserPaymentConfig 用户的商户凭据(一对一绑定 User)

func GetUserPaymentConfig

func GetUserPaymentConfig(ctx context.Context, userID uint64) (*UserPaymentConfig, error)

GetUserPaymentConfig 读取指定用户的支付配置,不存在返回 (nil, nil)。

func (UserPaymentConfig) TableName

func (UserPaymentConfig) TableName() string

TableName 自定义表名

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL