整改三期:退款、后台增改删、悬赏
退款原来只有会员那条路,而且只能全额、只改本地状态。现在代喂任务的退款 能分几次退到退完:每一笔单独记一行(谁退的、多少、走哪条通道、为什么), 订单上的 refunded_cent 跟着累加,退完且未结算的任务才关闭。渠道接通时按 金额原路退回并带独立幂等键,没接通或沙箱支付的登记为"线下已退"——假装 调用了渠道比说实话更糟。已结算的任务不能顺手退:钱在喂养者账上,要退只能 由平台承担,必须显式勾选。 后台从"只能通过或驳回"变成能改能删:宠物档案可代建、可编辑、可软删除 (有进行中任务的删不掉),送养与配种能改文案和城市、能下架能删,代喂任务 能改备注、能取消(托管中的钱必须先退)。每一个写操作都进审计日志。 悬赏按方案里的 A 做:主人自愿加的钱跟着任务一起托管,结算时全额给喂养者, 平台服务费只算在基础报酬上。抽悬赏是喂养者一眼能算出来的事。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ebf7189533
commit
a889cb42e4
@@ -37,8 +37,10 @@ type feedTaskView struct {
|
||||
VisitsPerDay int `json:"visitsPerDay"`
|
||||
MinMinutes int `json:"minMinutes"`
|
||||
PriceCent int64 `json:"priceCent"`
|
||||
TipCent int64 `json:"tipCent"`
|
||||
GrossCent int64 `json:"grossCent"`
|
||||
PayoutCent int64 `json:"payoutCent"`
|
||||
RefundedCent int64 `json:"refundedCent"`
|
||||
Note string `json:"note"`
|
||||
Status string `json:"status"`
|
||||
SitterUserID int64 `json:"sitterUserId"`
|
||||
@@ -238,6 +240,7 @@ func (a *App) createFeedTask(w http.ResponseWriter, r *http.Request) {
|
||||
VisitsPerDay int `json:"visitsPerDay"`
|
||||
MinMinutes int `json:"minMinutes"`
|
||||
PriceCent int64 `json:"priceCent"`
|
||||
TipCent int64 `json:"tipCent"`
|
||||
Note string `json:"note"`
|
||||
Items []struct {
|
||||
Kind string `json:"kind"`
|
||||
@@ -308,9 +311,19 @@ func (a *App) createFeedTask(w http.ResponseWriter, r *http.Request) {
|
||||
fail(w, http.StatusNotFound, 30001, "宠物不存在")
|
||||
return
|
||||
}
|
||||
if req.TipCent < 0 {
|
||||
req.TipCent = 0
|
||||
}
|
||||
if maxTip := int64(a.configInt(r.Context(), "pet.feed_tip_max_cent", 20000)); req.TipCent > maxTip {
|
||||
fail(w, http.StatusBadRequest, 20001, fmt.Sprintf("悬赏最多 %.0f 元", float64(maxTip)/100))
|
||||
return
|
||||
}
|
||||
totalVisits := days * req.VisitsPerDay
|
||||
gross := req.PriceCent * int64(totalVisits)
|
||||
fee, payout := a.feedFeeSplit(r.Context(), gross)
|
||||
base := req.PriceCent * int64(totalVisits)
|
||||
// 服务费只算在基础报酬上:悬赏是主人给喂养者的,平台不从中抽成。
|
||||
fee, basePayout := a.feedFeeSplit(r.Context(), base)
|
||||
gross := base + req.TipCent
|
||||
payout := basePayout + req.TipCent
|
||||
|
||||
tx, err := a.db.BeginTx(r.Context(), nil)
|
||||
if err != nil {
|
||||
@@ -319,10 +332,10 @@ func (a *App) createFeedTask(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
result, err := tx.ExecContext(r.Context(), `INSERT INTO pet_feed_tasks
|
||||
(owner_user_id,pet_id,address_id,city_code,start_date,end_date,visits_per_day,min_minutes,price_cent,gross_cent,platform_fee_cent,payout_cent,note,total_visits)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
(owner_user_id,pet_id,address_id,city_code,start_date,end_date,visits_per_day,min_minutes,price_cent,tip_cent,gross_cent,platform_fee_cent,payout_cent,note,total_visits)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
current(r).ID, req.PetID, req.AddressID, cityCode, start, end, req.VisitsPerDay, req.MinMinutes,
|
||||
req.PriceCent, gross, fee, payout, strings.TrimSpace(req.Note), totalVisits)
|
||||
req.PriceCent, req.TipCent, gross, fee, payout, strings.TrimSpace(req.Note), totalVisits)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, 50001, "创建失败")
|
||||
return
|
||||
@@ -350,11 +363,12 @@ func (a *App) createFeedTask(w http.ResponseWriter, r *http.Request) {
|
||||
fail(w, http.StatusInternalServerError, 50001, "创建失败")
|
||||
return
|
||||
}
|
||||
reply(w, map[string]any{"id": taskID, "grossCent": gross, "platformFeeCent": fee, "payoutCent": payout, "totalVisits": totalVisits})
|
||||
reply(w, map[string]any{"id": taskID, "grossCent": gross, "tipCent": req.TipCent,
|
||||
"platformFeeCent": fee, "payoutCent": payout, "totalVisits": totalVisits})
|
||||
}
|
||||
|
||||
const feedTaskColumns = `t.id,t.owner_user_id,owner.nickname,t.pet_id,p.name,p.species,p.avatar_url,addr.city_name,addr.area_text,addr.latitude IS NOT NULL,
|
||||
t.start_date,t.end_date,t.visits_per_day,t.min_minutes,t.price_cent,t.gross_cent,t.payout_cent,t.note,t.status,
|
||||
t.start_date,t.end_date,t.visits_per_day,t.min_minutes,t.price_cent,t.tip_cent,t.gross_cent,t.payout_cent,t.refunded_cent,t.note,t.status,
|
||||
COALESCE(t.sitter_user_id,0),COALESCE(sitter.nickname,''),COALESCE(t.conversation_id,0),t.applications_count,t.total_visits,t.completed_visits,t.created_at`
|
||||
|
||||
func (a *App) scanFeedTasks(ctx context.Context, rows *sql.Rows, viewer int64) []feedTaskView {
|
||||
@@ -367,7 +381,7 @@ func (a *App) scanFeedTasks(ctx context.Context, rows *sql.Rows, viewer int64) [
|
||||
var created time.Time
|
||||
if rows.Scan(&item.ID, &item.OwnerUserID, &item.OwnerName, &item.PetID, &item.PetName, &item.Species, &item.PetAvatar,
|
||||
&item.CityName, &item.AreaText, &item.Located, &start, &end, &item.VisitsPerDay, &item.MinMinutes, &item.PriceCent,
|
||||
&item.GrossCent, &item.PayoutCent, &item.Note, &item.Status, &item.SitterUserID, &item.SitterName,
|
||||
&item.TipCent, &item.GrossCent, &item.PayoutCent, &item.RefundedCent, &item.Note, &item.Status, &item.SitterUserID, &item.SitterName,
|
||||
&item.Conversation, &item.Applications, &item.TotalVisits, &item.Done, &created) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user