增加发布协议确认配置
This commit is contained in:
@@ -47,6 +47,9 @@ type CreateRequest struct {
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
Price float64 `json:"price"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
|
||||
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
|
||||
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
|
||||
}
|
||||
|
||||
type UpdateRequest = CreateRequest
|
||||
|
||||
@@ -577,6 +577,8 @@ func writeListingError(c *gin.Context, err error) {
|
||||
response.BadRequest(c, "哈夫币数量不正确")
|
||||
case errors.Is(err, ErrMissingScreenshot):
|
||||
response.BadRequest(c, "请至少上传一张账号截图")
|
||||
case errors.Is(err, ErrAgreementRequired):
|
||||
response.BadRequest(c, "请先阅读并同意发布协议")
|
||||
case errors.Is(err, ErrMissingUploaderName):
|
||||
response.BadRequest(c, "上传人名称不能为空")
|
||||
case errors.Is(err, ErrMissingUploadData):
|
||||
|
||||
@@ -22,6 +22,7 @@ var (
|
||||
ErrDepositTooLow = errors.New("listing deposit too low")
|
||||
ErrInvalidHafCoin = errors.New("invalid haf coin amount")
|
||||
ErrMissingScreenshot = errors.New("missing screenshot")
|
||||
ErrAgreementRequired = errors.New("listing publish agreement required")
|
||||
ErrMissingUploaderName = errors.New("missing uploader name")
|
||||
ErrMissingUploadData = errors.New("missing upload data")
|
||||
ErrUploaderNotFound = errors.New("uploader not found")
|
||||
@@ -80,6 +81,9 @@ func (s *Service) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error)
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if !req.AgreedVirtualAssetSale || !req.AgreedSellerAgreement {
|
||||
return nil, ErrAgreementRequired
|
||||
}
|
||||
rules, err := s.publishRules()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -58,6 +58,15 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRequiresPublishAgreements(t *testing.T) {
|
||||
service := NewService(&Repository{}, nil)
|
||||
|
||||
_, err := service.Create(1, CreateRequest{})
|
||||
if err != ErrAgreementRequired {
|
||||
t.Fatalf("expected ErrAgreementRequired, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySellerListingPriceUsesSellerTotalPrice(t *testing.T) {
|
||||
item := &ListingDTO{
|
||||
Price: 238,
|
||||
|
||||
@@ -32,6 +32,11 @@ type OrderAgreementsDTO struct {
|
||||
RenterAgreement AgreementContentDTO `json:"renter_agreement"`
|
||||
}
|
||||
|
||||
type ListingPublishAgreementsDTO struct {
|
||||
VirtualAssetSale AgreementContentDTO `json:"virtual_asset_sale"`
|
||||
SellerAgreement AgreementContentDTO `json:"seller_agreement"`
|
||||
}
|
||||
|
||||
type PostRentalNoticeDTO struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
|
||||
@@ -54,6 +54,15 @@ func (h *Handler) OrderAgreements(c *gin.Context) {
|
||||
response.OK(c, agreements)
|
||||
}
|
||||
|
||||
func (h *Handler) ListingPublishAgreements(c *gin.Context) {
|
||||
agreements, err := h.service.ListingPublishAgreements()
|
||||
if err != nil {
|
||||
writeConfigError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, agreements)
|
||||
}
|
||||
|
||||
func (h *Handler) PostRentalNotice(c *gin.Context) {
|
||||
notice, err := h.service.PostRentalNotice()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package systemconfig
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const listingPublishAgreementsConfigKey = "listing.publish_agreements"
|
||||
|
||||
func defaultListingPublishAgreementsConfigValue() string {
|
||||
raw, err := json.Marshal(DefaultListingPublishAgreements())
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
func DefaultListingPublishAgreements() ListingPublishAgreementsDTO {
|
||||
return ListingPublishAgreementsDTO{
|
||||
VirtualAssetSale: AgreementContentDTO{
|
||||
Title: "虚拟资产出售协议",
|
||||
Content: `请您在发布账号前仔细阅读并确认以下内容:
|
||||
|
||||
一、发布性质
|
||||
1. 您发布的账号、哈夫币、皮肤、装备、消耗品等均属于游戏内虚拟资产或使用权益展示。
|
||||
2. 虚拟资产受游戏厂商规则、版本更新、风控策略和账号状态影响,平台不承诺其具备现实货币价值或永久可用性。
|
||||
3. 您应确保发布信息真实、完整、可交接,且与截图材料和账号实际资产一致。
|
||||
|
||||
二、风险告知
|
||||
1. 发布账号后,买家下单前后可能因游戏环境变化、资产变动、封禁记录、登录限制等产生交易风险。
|
||||
2. 若您隐瞒账号异常、资产缺失、封禁记录、不可用物资或其他影响交易的信息,平台可依据证据处理赔付、退款或下架。
|
||||
3. 请勿绕过平台私下收款、私下交接或诱导用户脱离平台沟通,否则平台可能限制账号发布权限。
|
||||
|
||||
三、费用与结算
|
||||
1. 发布价格、押金、额外消耗品价值以平台发布页和订单页面展示为准。
|
||||
2. 订单完成后,平台将按订单记录、结账结果和相关规则进行结算。
|
||||
3. 若发生争议,平台将依据订单记录、资产快照、聊天记录、截图证据和双方说明进行处理。`,
|
||||
},
|
||||
SellerAgreement: AgreementContentDTO{
|
||||
Title: "号主协议",
|
||||
Content: `请您作为号主在发布账号前确认并遵守以下约定:
|
||||
|
||||
一、账号发布义务
|
||||
1. 您应如实填写区服、段位、哈夫币数量、保险、体力、负重、登录方式、封禁记录、常用地区和备注信息。
|
||||
2. 您应上传真实、清晰、可核验的账号截图材料,不得使用伪造、过期或与账号不一致的截图。
|
||||
3. 您不得发布盗号、黑号、纠纷账号、已被限制使用账号或无法完成正常交接的账号。
|
||||
|
||||
二、交接与协作
|
||||
1. 买家下单后,您应在平台要求时间内完成交接,及时响应扫码、人脸冻结、登录验证等流程。
|
||||
2. 您应在租用期间保持可联系状态,不得无故拒绝交接、恶意拖延或私下更改交易条件。
|
||||
3. 租用结束后,请按平台流程确认归还和结账,配合核验资产状态。
|
||||
|
||||
三、责任承担
|
||||
1. 因您发布信息不实、交接不及时、账号异常隐瞒或私下交易造成的损失,平台可依据规则处理退款、赔付、下架或限制发布。
|
||||
2. 因租客违规使用造成的损失,您应通过平台流程提交证据并配合争议处理。
|
||||
3. 您同意平台依据订单记录、资产快照、聊天记录、截图证据和相关规则处理结算、赔付及争议。`,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: "order.pending_payment_timeout_minutes", Value: "15", Description: "订单待支付超时取消分钟数"},
|
||||
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
|
||||
{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
|
||||
{Key: listingPublishAgreementsConfigKey, Value: defaultListingPublishAgreementsConfigValue(), Description: "发布账号前协议配置 JSON"},
|
||||
{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
|
||||
{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
|
||||
{Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID(仅当无可用客服时使用)"},
|
||||
@@ -47,6 +48,7 @@ var adminVisibleConfigKeys = []string{
|
||||
"handoff.owner_return_confirm_timeout_minutes",
|
||||
"handoff.owner_submit_timeout_minutes",
|
||||
"handoff.renter_confirm_timeout_minutes",
|
||||
"listing.publish_agreements",
|
||||
"listing.publish_options",
|
||||
"listing.review_required",
|
||||
"listing.sale_price_config",
|
||||
|
||||
@@ -59,6 +59,17 @@ func (s *Service) OrderAgreements() (*OrderAgreementsDTO, error) {
|
||||
return &agreements, nil
|
||||
}
|
||||
|
||||
func (s *Service) ListingPublishAgreements() (*ListingPublishAgreementsDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
agreements, err := s.listingPublishAgreements()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &agreements, nil
|
||||
}
|
||||
|
||||
func (s *Service) PostRentalNotice() (*PostRentalNoticeDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
@@ -109,6 +120,19 @@ func (s *Service) orderAgreements() (OrderAgreementsDTO, error) {
|
||||
return agreements, nil
|
||||
}
|
||||
|
||||
func (s *Service) listingPublishAgreements() (ListingPublishAgreementsDTO, error) {
|
||||
value, err := s.repo.FindValue(listingPublishAgreementsConfigKey)
|
||||
if err != nil {
|
||||
return ListingPublishAgreementsDTO{}, err
|
||||
}
|
||||
agreements := DefaultListingPublishAgreements()
|
||||
if err := json.Unmarshal([]byte(value), &agreements); err != nil {
|
||||
agreements = DefaultListingPublishAgreements()
|
||||
}
|
||||
normalizeListingPublishAgreements(&agreements)
|
||||
return agreements, nil
|
||||
}
|
||||
|
||||
func (s *Service) postRentalNotice() (PostRentalNoticeDTO, error) {
|
||||
value, err := s.repo.FindValue(postRentalNoticeConfigKey)
|
||||
if err != nil {
|
||||
@@ -247,6 +271,26 @@ func normalizeOrderAgreements(agreements *OrderAgreementsDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeListingPublishAgreements(agreements *ListingPublishAgreementsDTO) {
|
||||
defaults := DefaultListingPublishAgreements()
|
||||
agreements.VirtualAssetSale.Title = strings.TrimSpace(agreements.VirtualAssetSale.Title)
|
||||
agreements.VirtualAssetSale.Content = strings.TrimSpace(agreements.VirtualAssetSale.Content)
|
||||
agreements.SellerAgreement.Title = strings.TrimSpace(agreements.SellerAgreement.Title)
|
||||
agreements.SellerAgreement.Content = strings.TrimSpace(agreements.SellerAgreement.Content)
|
||||
if agreements.VirtualAssetSale.Title == "" {
|
||||
agreements.VirtualAssetSale.Title = defaults.VirtualAssetSale.Title
|
||||
}
|
||||
if agreements.VirtualAssetSale.Content == "" {
|
||||
agreements.VirtualAssetSale.Content = defaults.VirtualAssetSale.Content
|
||||
}
|
||||
if agreements.SellerAgreement.Title == "" {
|
||||
agreements.SellerAgreement.Title = defaults.SellerAgreement.Title
|
||||
}
|
||||
if agreements.SellerAgreement.Content == "" {
|
||||
agreements.SellerAgreement.Content = defaults.SellerAgreement.Content
|
||||
}
|
||||
}
|
||||
|
||||
func normalizePostRentalNotice(notice *PostRentalNoticeDTO) {
|
||||
defaults := DefaultPostRentalNotice()
|
||||
notice.Title = strings.TrimSpace(notice.Title)
|
||||
|
||||
@@ -240,6 +240,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
{
|
||||
api.GET("/health", health.Check)
|
||||
api.GET("/listing-publish-options", systemConfigHandler.PublishOptions)
|
||||
api.GET("/listing-publish-agreements", systemConfigHandler.ListingPublishAgreements)
|
||||
api.GET("/listing-sale-price-config", systemConfigHandler.SalePriceConfig)
|
||||
api.GET("/order-agreements", systemConfigHandler.OrderAgreements)
|
||||
api.GET("/post-rental-notice", systemConfigHandler.PostRentalNotice)
|
||||
|
||||
Reference in New Issue
Block a user