167 lines
6.1 KiB
Go
167 lines
6.1 KiB
Go
package recharge
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"time"
|
|
|
|
integrationbiz "kra/internal/biz/integration"
|
|
bizpayment "kra/internal/biz/payment"
|
|
biz "kra/internal/biz/recharge"
|
|
"kra/internal/service/dto"
|
|
)
|
|
|
|
type Service struct {
|
|
uc *biz.Usecase
|
|
integrations *integrationbiz.IntegrationConfigUsecase
|
|
}
|
|
|
|
func NewService(uc *biz.Usecase, integrations *integrationbiz.IntegrationConfigUsecase) *Service {
|
|
return &Service{uc: uc, integrations: integrations}
|
|
}
|
|
|
|
func (s *Service) Providers(ctx context.Context) ([]*dto.RechargeProvider, error) {
|
|
configs, err := s.integrations.List(ctx, integrationbiz.IntegrationKindPayment)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
providers := make([]*dto.RechargeProvider, 0, len(configs))
|
|
seen := make(map[string]struct{}, len(configs))
|
|
for _, config := range configs {
|
|
if config == nil || !config.Enabled || !bizpayment.IsSupportedPaymentProvider(config.Provider) {
|
|
continue
|
|
}
|
|
definition, ok := integrationbiz.IntegrationDefinition(integrationbiz.IntegrationKindPayment, config.Provider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, exists := seen[definition.Provider]; exists {
|
|
continue
|
|
}
|
|
seen[definition.Provider] = struct{}{}
|
|
providers = append(providers, &dto.RechargeProvider{ID: definition.Provider, Name: definition.Name})
|
|
}
|
|
sort.Slice(providers, func(i, j int) bool { return providers[i].ID < providers[j].ID })
|
|
return providers, nil
|
|
}
|
|
|
|
func dates(a, b string) []time.Time {
|
|
if a == "" || b == "" {
|
|
return nil
|
|
}
|
|
x, e1 := time.ParseInLocation("2006-01-02", a, time.Local)
|
|
y, e2 := time.ParseInLocation("2006-01-02", b, time.Local)
|
|
if e1 != nil || e2 != nil {
|
|
return nil
|
|
}
|
|
return []time.Time{x, y.Add(24*time.Hour - time.Nanosecond)}
|
|
}
|
|
|
|
func options(v dto.LiveListRequest) biz.ListOptions {
|
|
p, size := v.Page, v.PageSize
|
|
if p < 1 {
|
|
p = 1
|
|
}
|
|
if size < 1 {
|
|
size = 10
|
|
}
|
|
return biz.ListOptions{Offset: (p - 1) * size, Limit: size, OrderBy: v.OrderBy}
|
|
}
|
|
|
|
func (s *Service) Plans(ctx context.Context) ([]*dto.RechargePlan, error) {
|
|
items, err := s.uc.Plans(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]*dto.RechargePlan, 0, len(items))
|
|
for _, v := range items {
|
|
out = append(out, &dto.RechargePlan{ID: v.ID, Sort: v.Sort, Diamonds: v.Diamonds, AmountCents: v.AmountCents, Enabled: v.Enabled})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Service) SavePlans(ctx context.Context, in *dto.RechargePlansRequest) error {
|
|
plans := make([]*biz.Plan, 0, len(in.Plans))
|
|
for _, v := range in.Plans {
|
|
plans = append(plans, &biz.Plan{ID: v.ID, Sort: v.Sort, Diamonds: v.Diamonds, AmountCents: v.AmountCents, Enabled: v.Enabled})
|
|
}
|
|
return s.uc.SavePlans(ctx, plans)
|
|
}
|
|
|
|
func (s *Service) List(ctx context.Context, in *dto.RechargeOrderListRequest) ([]*dto.RechargeOrderResponse, int64, *dto.RechargeSummary, error) {
|
|
items, total, sum, err := s.uc.List(ctx, biz.Filter{OrderNo: in.OrderNo, UserKeyword: in.UserKeyword, PaymentStatus: in.PaymentStatus, Provider: in.Provider, CreatedAt: dates(in.StartedAt, in.EndedAt)}, options(in.LiveListRequest))
|
|
if err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
out := make([]*dto.RechargeOrderResponse, 0, len(items))
|
|
for _, v := range items {
|
|
out = append(out, order(v))
|
|
}
|
|
return out, total, &dto.RechargeSummary{Count: sum.Count, PaidCount: sum.PaidCount, AmountCents: sum.AmountCents}, nil
|
|
}
|
|
|
|
func (s *Service) Get(ctx context.Context, no string) (*dto.RechargeOrderResponse, error) {
|
|
v, err := s.uc.Get(ctx, no)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return order(v), nil
|
|
}
|
|
|
|
// GetForUser is the client-facing order lookup. It deliberately maps an
|
|
// order owned by another account to not-found so order numbers cannot be used
|
|
// to enumerate another user's payment or diamond balance details.
|
|
func (s *Service) GetForUser(ctx context.Context, userID uint, no string) (*dto.RechargeOrderResponse, error) {
|
|
if userID == 0 {
|
|
return nil, biz.ErrNotFound
|
|
}
|
|
item, err := s.uc.Get(ctx, no)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item.UserID != userID {
|
|
return nil, biz.ErrNotFound
|
|
}
|
|
return order(item), nil
|
|
}
|
|
|
|
// PaymentStatus is an explicit alias for client polling while a provider
|
|
// payment is pending. The response includes both provider and fulfillment
|
|
// state through the normal recharge order DTO.
|
|
func (s *Service) PaymentStatus(ctx context.Context, userID uint, no string) (*dto.RechargeOrderResponse, error) {
|
|
return s.GetForUser(ctx, userID, no)
|
|
}
|
|
|
|
func (s *Service) Create(ctx context.Context, userID uint, in *dto.RechargeOrderRequest) (*dto.RechargeCheckoutResponse, error) {
|
|
v, result, err := s.uc.Create(ctx, userID, in.PlanID, biz.Checkout{Provider: in.Provider, NotifyURL: in.NotifyURL, ReturnURL: in.ReturnURL, ClientIP: in.ClientIP, UserAgent: in.UserAgent, DeviceID: in.DeviceID, Extra: in.Extra})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto.RechargeCheckoutResponse{Order: order(v), Payment: paymentResult(result)}, nil
|
|
}
|
|
|
|
func order(v *biz.Order) *dto.RechargeOrderResponse {
|
|
return &dto.RechargeOrderResponse{
|
|
ID: v.ID, OrderNo: v.OrderNo, UserID: v.UserID, Username: v.Username, Nickname: v.Nickname,
|
|
Diamonds: v.Diamonds, AmountCents: v.AmountCents, Provider: v.Provider, ProviderTradeNo: v.ProviderTradeNo,
|
|
PaymentStatus: v.PaymentStatus, FulfillmentStatus: v.FulfillmentStatus, RefundStatus: v.RefundStatus,
|
|
CreatedAt: v.CreatedAt, CreditedAt: v.CreditedAt,
|
|
}
|
|
}
|
|
|
|
func paymentResult(result *bizpayment.PaymentResult) *dto.PaymentResultResponse {
|
|
if result == nil {
|
|
return nil
|
|
}
|
|
return &dto.PaymentResultResponse{
|
|
Provider: result.Provider, Status: result.Status, TradeNo: result.TradeNo,
|
|
ProviderTradeNo: result.ProviderTradeNo, Amount: result.Amount,
|
|
PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount,
|
|
PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount,
|
|
ProviderDiscountAmount: result.ProviderDiscountAmount, MerchantDiscountAmount: result.MerchantDiscountAmount,
|
|
SettlementAmount: result.SettlementAmount, Currency: result.Currency, PayerCurrency: result.PayerCurrency,
|
|
AmountBreakdownKnown: result.AmountBreakdownKnown, Duplicate: result.Duplicate, Payload: result.Payload,
|
|
OrderStatus: result.OrderStatus, FulfillmentStatus: result.FulfillmentStatus, RefundStatus: result.RefundStatus,
|
|
}
|
|
}
|