diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..4bb078b --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,153 @@ +run: + timeout: 3m + # Exit code when at least one issue was found. + # Default: 1 + issues-exit-code: 2 + # Include test files or not. + # Default: true + tests: false + +# Reference URL: https://golangci-lint.run/usage/linters/ +linters: + # Disable everything by default so upgrades to not include new - default + # enabled- linters. + disable-all: true + # Specifically enable linters we want to use. + enable: + # - depguard + - errcheck + # - godot + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - misspell + - revive + # - staticcheck + - typecheck + - unused + # - wsl + - asasalint + - asciicheck + - bidichk + - bodyclose + # - containedctx + - contextcheck + # - cyclop + # - varnamelen + # - gci + - wastedassign + - whitespace + # - wrapcheck + - thelper + - tparallel + - unconvert + - unparam + - usestdlibvars + - tenv + - testableexamples + - stylecheck + - sqlclosecheck + - nosprintfhostport + - paralleltest + - prealloc + - predeclared + - promlinter + - reassign + - rowserrcheck + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - decorder + - dogsled + # - dupl + - dupword + - durationcheck + - errchkjson + - errname + - errorlint + # - execinquery + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + # - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + # - godox + # - goerr113 + # - gofumpt + - goheader + - gomoddirectives + # - gomodguard always failed + - goprintffuncname + - gosec + - grouper + - importas + - interfacebloat + # - ireturn + - lll + - loggercheck + - maintidx + - makezero + +issues: + exclude-rules: + - path: _test\.go + linters: + - funlen + - goconst + - interfacer + - dupl + - lll + - goerr113 + - errcheck + - gocritic + - cyclop + - wrapcheck + - gocognit + - contextcheck + + exclude-dirs: + - internal/model + - internal/logic/couponservice + - internal/logic/inventoryservice + - internal/logic/productservice + - internal/logic/subscriptionservice + - internal/logic/walletservice + + exclude-files: + - .*_test.go + + + +linters-settings: + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 40 + nestif: + # Minimal complexity of if statements to report. + # Default: 5 + min-complexity: 10 + lll: + # Max line length, lines longer will be reported. + # '\t' is counted as 1 character by default, and can be changed with the tab-width option. + # Default: 120. + line-length: 200 + # Tab width in spaces. + # Default: 1 + tab-width: 1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eac2065 --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +GO_CTL_NAME=goctl + +# go-zero 生成風格 +GO_ZERO_STYLE=go_zero + +GO ?= go +GOFMT ?= gofmt "-s" +GOFILES := $(shell find . -name "*.go") +LDFLAGS := -s -w +VERSION="v1.0.1" +DOCKER_REPO="igs170911/feed" + +.PHONY: test +test: # 進行測試 + go test -v --cover ./... + +.PHONY: fmt +fmt: # 格式優化 + $(GOFMT) -w $(GOFILES) + goimports -w ./ + golangci-lint run + +.PHONY: gen-rpc +gen-rpc: # 建立 rpc code + $(GO_CTL_NAME) rpc protoc ./generate/protobuf/trade.proto -m --style=$(GO_ZERO_STYLE) --go_out=./gen_result/pb --go-grpc_out=./gen_result/pb --zrpc_out=. + go mod tidy + @echo "Generate core-api files successfully" + +.PHONY: gen-clean +gen-clean: # 建立 rpc code + rm -rf ./client + rm -rf ./etc + rm -rf ./gen_result + rm -rf ./internal + rm -rf go.mod + rm -rf go.sum + rm -rf order.go + @echo "Generate core-api files successfully" + +.PHONY: run-docker +run-docker: # 建立 rpc code + docker run --platform=linux/arm64/v8 -p 8080:8080 $(DOCKER_REPO):$(VERSION) + +.PHONY: build-docker +build-docker: + cp ./build/Dockerfile Dockerfile + docker buildx build -t $(DOCKER_REPO):$(VERSION) --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/ed_25519)" . + rm -rf Dockerfile + @echo "Generate core-api files successfully" + +gen-mongo-model: # 建立 rpc 資料庫 + # 只產生 Model 剩下的要自己撰寫,連欄位名稱也是 + goctl model mongo -t order --dir ./internal/model/mongo --style $(GO_ZERO_STYLE) + @echo "Generate mongo model files successfully" + +.PHONY: mock-gen +mock-gen: # 建立 mock 資料 + mockgen -source=./internal/model/mongo/order_model_gen.go -destination=./internal/mock/model/order_model_gen.go -package=mock + mockgen -source=./internal/model/mongo/order_model.go -destination=./internal/mock/model/order_model.go -package=mock + @echo "Generate mock files successfully" + +.PHONY: migrate-database +migrate-database: + migrate -source file://generate/database/mongodb -database 'mongodb://127.0.0.1:27017/digimon_order' up diff --git a/etc/trade.yaml b/etc/trade.yaml new file mode 100644 index 0000000..595e41c --- /dev/null +++ b/etc/trade.yaml @@ -0,0 +1,29 @@ +Name: trade.rpc +ListenOn: 0.0.0.0:8080 +Etcd: + Hosts: + - 127.0.0.1:2379 + Key: trade.rpc + +mongodb: + hosts: [ 127.0.0.1:27017 ] + username: + password: + databaseName: "cnx_commission" + minPoolSize: 10 + maxPoolSize: 30 + maxConnIdleTime: 30m + replicaName: "" + +Mongo: + Schema: mongodb + Host: 127.0.0.1 + User: "" + Password: "" + Port: "27017" + Database: digimon_order + ReplicaName: "order" + MaxStaleness: 30m + MaxPoolSize: 30 + MinPoolSize: 10 + MaxConnIdleTime: 30m diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..640d4fa --- /dev/null +++ b/go.mod @@ -0,0 +1,108 @@ +module app-cloudep-trade-service + +go 1.22.3 + +require ( + code.30cm.net/digimon/library-go/errs v1.2.5 + code.30cm.net/digimon/library-go/validator v1.0.0 + github.com/go-playground/assert/v2 v2.2.0 + github.com/go-playground/validator/v10 v10.22.0 + github.com/shopspring/decimal v1.4.0 + github.com/zeromicro/go-zero v1.7.3 + go.mongodb.org/mongo-driver v1.17.1 + go.uber.org/mock v0.5.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openzipkin/zipkin-go v0.4.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/redis/go-redis/v9 v9.7.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.etcd.io/etcd/api/v3 v3.5.15 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect + go.etcd.io/etcd/client/v3 v3.5.15 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.29.3 // indirect + k8s.io/apimachinery v0.29.4 // indirect + k8s.io/client-go v0.29.3 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100755 index 0000000..516850b --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,27 @@ +package config + +import ( + "time" + + "github.com/zeromicro/go-zero/zrpc" +) + +type Config struct { + zrpc.RpcServerConf + + Mongo struct { + Schema string + User string + Password string + Host string + Port string + Database string + ReplicaName string + MaxStaleness time.Duration + MaxPoolSize uint64 + MinPoolSize uint64 + MaxConnIdleTime time.Duration + // Compressors []string + // EnableStandardReadWriteSplitMode bool + } +} diff --git a/internal/domain/errors.go b/internal/domain/errors.go new file mode 100644 index 0000000..050f912 --- /dev/null +++ b/internal/domain/errors.go @@ -0,0 +1,45 @@ +package domain + +import ( + "strings" + + ers "code.30cm.net/digimon/library-go/errs" + "code.30cm.net/digimon/library-go/errs/code" + "github.com/zeromicro/go-zero/core/logx" +) + +type ErrorCode uint32 + +func (e ErrorCode) ToUint32() uint32 { + return uint32(e) +} + +// Error Code 統一這邊改 +const ( + _ = iota + CreateOrderErrorCode ErrorCode = iota + CancelOrderErrorCode + ModifyOrderErrorCode + TimeoutOrderErrorCode +) + +const ( + _ ErrorCode = 10 + iota + DataNotFoundErrorCode +) + +func CommentError(ec ErrorCode, s ...string) *ers.LibError { + return ers.NewError(code.CloudEPOrder, code.DBError, ec.ToUint32(), strings.Join(s, " ")) +} + +func CommentErrorL(ec ErrorCode, + l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError { + e := CommentError(ec, s...) + l.WithCallerSkip(1).WithFields(filed...).Error(e.Error()) + + return e +} + +func NotFoundError(ec ErrorCode, s ...string) *ers.LibError { + return ers.NewError(code.CloudEPOrder, code.ResourceNotFound, ec.ToUint32(), strings.Join(s, " ")) +} diff --git a/internal/domain/order.go b/internal/domain/order.go new file mode 100644 index 0000000..97f10ec --- /dev/null +++ b/internal/domain/order.go @@ -0,0 +1,32 @@ +package domain + +type OrderStatus int64 + +func (o *OrderStatus) ToInt64() int64 { + return int64(*o) +} + +const ( + OrderStatusCreated OrderStatus = 0 // 建立訂單 + OrderStatusFailed OrderStatus = 1 // 建單失敗 + OrderStatusReviewing OrderStatus = 2 // 審核中 + OrderStatusPaying OrderStatus = 3 // 付款中 + OrderStatusPaid OrderStatus = 4 // 已付款 + OrderStatusPendingTransfer OrderStatus = 5 // 已付款待轉帳 + OrderStatusDisputing OrderStatus = 6 // 申訴中 + OrderStatusCompleted OrderStatus = 7 // 交易完成 + OrderStatusFailedTrade OrderStatus = 8 // 交易失敗 + OrderStatusCancelled OrderStatus = 9 // 交易取消 + OrderStatusAbnormal OrderStatus = 10 // 交易異常 + OrderStatusTimeout OrderStatus = 11 // 交易超時 +) + +type OrderType int64 + +const ( + OrderTypeTest OrderType = 0 // 測試訂單 +) + +func (o *OrderType) ToInt() int { + return int(*o) +} diff --git a/internal/domain/usecase/order.go b/internal/domain/usecase/order.go new file mode 100644 index 0000000..6d1db4f --- /dev/null +++ b/internal/domain/usecase/order.go @@ -0,0 +1,165 @@ +package usecase + +import ( + "app-cloudep-trade-service/internal/domain" + "context" + + "github.com/shopspring/decimal" +) + +type OrderUseCase interface { + // CreateOrder 建立訂單,不另外做 struct 直接用 model.Order + // 考量底層已經寫完,如有額外需求需要在這邊用轉的 + CreateOrder(ctx context.Context, param CreateOrderReq) error + // CancelOrder 取消訂單 + CancelOrder(ctx context.Context, param CancelOrderQuery) error + // DeleteOrder 刪除訂單(軟刪除) + DeleteOrder(ctx context.Context, param DeleteOrderQuery) error + // GetOrder 取得訂單資料 + GetOrder(ctx context.Context, param GetOrderQuery) (*GetOrderResp, error) + // ListOrder 取得資料列表 + ListOrder(ctx context.Context, param GetOrderListReq) (*ListOrderResp, error) + // ModifyOrderStatus 更新資料 + ModifyOrderStatus(ctx context.Context, param *ModifyOrderQuery) error + // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout + OrderStatusTimeout(ctx context.Context) error +} + +// ModifyOrderQuery +// 0.建立訂單 1.建單失敗 2.審核中 3.付款中 4.已付款 +// 5.已付款待轉帳 6.申訴中 7.交易完成 +// 8.交易失敗 9.交易取消 10.交易異常 11.交易超時 + +type ModifyOrderQuery struct { + BusinessID string `json:"business_id" validate:"required"` + Status int64 `json:"status" validate:"required,oneof=2 3 4 5 6 7 8 11"` +} + +// CancelOrderQuery 1.建單失敗 9.交易取消 10.交易異常 +type CancelOrderQuery struct { + BusinessID string + Status domain.OrderStatus +} + +// DeleteOrderQuery 刪除訂單(軟刪除) +type DeleteOrderQuery struct { + BusinessID string +} + +// GetOrderQuery 取得訂單 +type GetOrderQuery struct { + BusinessID string +} + +type GetOrderResp struct { + BusinessID string // 訂單業務流水號 + OrderType domain.OrderType `json:"order_type"` // 訂單類型 + OrderStatus domain.OrderStatus `json:"order_status"` // 訂單狀態 + Brand string `json:"brand"` // 下單平台 + OrderUID string `json:"order_uid"` // 下單用戶 UID + ReferenceID string `json:"reference_id"` // 訂單來源 + Count string `json:"count"` // 訂單數量 (decimal to string) + OrderFee string `json:"order_fee"` // 訂單手續費 (decimal to string) + Amount string `json:"amount"` // 單價 (decimal to string) + ReferenceBrand *string `json:"reference_brand,omitempty"` // 訂單來源平台 + ReferenceUID *string `json:"reference_uid,omitempty"` // 訂單來源用戶 UID + WalletStatus *int64 `json:"wallet_status,omitempty"` // 交易金額狀態 + ThreePartyStatus *int64 `json:"three_party_status,omitempty"` // 三方請求狀態 + DirectionType *int64 `json:"direction_type,omitempty"` // 交易方向 + CryptoType *string `json:"crypto_type,omitempty"` // 交易幣種 + ThirdPartyFee *string `json:"third_party_fee,omitempty"` // 第三方手續費 (decimal to string) + CryptoToUsdtRate *string `json:"crypto_to_usdt_rate,omitempty"` // 交易幣種對 USDT 匯率 (decimal to string) + FiatToUsdRate *string `json:"fiat_to_usd_rate,omitempty"` // 法幣對 USD 匯率 (decimal to string) + FeeCryptoToUsdtRate *string `json:"fee_crypto_to_usdt_rate,omitempty"` // 手續費幣種對 USDT 匯率 (decimal to string) + UsdtToCryptoTypeRate *string `json:"usdt_to_crypto_type_rate,omitempty"` // USDT 對交易幣種匯率 (decimal to string) + PaymentFiat *string `json:"payment_fiat,omitempty"` // 支付法幣 + PaymentUnitPrice *string `json:"payment_unit_price,omitempty"` // crypto 單價 (decimal to string) + PaymentTemplateID *string `json:"payment_template_id,omitempty"` // 支付方式配置 ID + OrderArrivalTime *int64 `json:"order_arrival_time,omitempty"` // 訂單到帳時間 + OrderPaymentTime *int64 `json:"order_payment_time,omitempty"` // 訂單付款時間 + UnpaidTimeoutSecond *int64 `json:"unpaid_timeout_second,omitempty"` // 支付期限秒數 + ChainType *string `json:"chain_type,omitempty"` // 主網類型 + TxHash *string `json:"tx_hash,omitempty,omitempty"` // 交易哈希 + FromAddress *string `json:"from_address,omitempty,omitempty"` // 來源地址 + ToAddress *string `json:"to_address,omitempty,omitempty"` // 目標地址 + ChainFee *string `json:"chain_fee,omitempty"` // 鏈上交易手續費 (decimal to string) + ChainFeeCrypto *string `json:"chain_fee_crypto,omitempty"` // 鏈上手續費使用幣別 + Memo *string `json:"memo,omitempty"` // 鏈上備註 + OrderNote *string `json:"order_note,omitempty"` // 訂單交易備註 + CreateTime int64 `json:"create_time,omitempty"` // 建立時間 + UpdateTime int64 `json:"update_time,omitempty"` // 更新時間 +} + +type GetOrderListReq struct { + PageIndex int64 + PageSize int64 + + ReferenceID string + ReferenceUID string + BusinessID string + UID string + OrderType domain.OrderType + DirectionType []int64 + OrderStatus []int64 + + StartCreateTime int64 + EndCreateTime int64 + StartUpdateTime int64 + EndUpdateTime int64 + StartOrderArrivalTime int64 + EndOrderArrivalTime int64 + StartOrderPaymentTime int64 + EndOrderPaymentTime int64 + + CryptoType string + TxHash string +} + +type Pager struct { + Index int64 + Size int64 + Total int64 +} + +type ListOrderResp struct { + Data []*GetOrderResp `json:"data"` // 訂單列表 + Page *Pager `json:"page"` +} + +type CreateOrderReq struct { + BusinessID string + OrderType domain.OrderType // 訂單類型 + OrderStatus domain.OrderStatus // 訂單狀態 + Brand string // 下單平台 + OrderUID string // 下單用戶 UID + ReferenceID string // 訂單來源 + Count decimal.Decimal // 訂單數量 + OrderFee decimal.Decimal // 訂單手續費 + Amount decimal.Decimal // 單價 + WalletStatus int64 // 交易金額狀態 + DirectionType int64 // 交易方向 + // 以上為必要欄位,下面是區塊鏈時才需要 + ReferenceBrand *string // 訂單來源平台 + ReferenceUID *string // 訂單來源用戶 UID + ThreePartyStatus *int64 // 三方請求狀態 + CryptoType *string // 交易幣種 + ThirdPartyFee *decimal.Decimal // 第三方手續費 + CryptoToUSDTRate *decimal.Decimal // 加密貨幣對 USDT 匯率 + FiatToUSDRate *decimal.Decimal // 法幣對 USD 匯率 + FeeCryptoToUSDTRate *decimal.Decimal // 手續費加密貨幣對 USDT 匯率 + USDTToCryptoTypeRate *decimal.Decimal // USDT 對加密貨幣匯率 + PaymentFiat *string // 支付法幣 + PaymentUnitPrice *decimal.Decimal // 加密貨幣單價 + PaymentTemplateID *string // 支付方式配置 ID + OrderArrivalTime *int64 // 訂單到帳時間 + OrderPaymentTime *int64 // 訂單付款時間 + UnpaidTimeoutSecond *int64 // 支付期限秒數 + ChainType *string // 主網類型 + TxHash *string // 交易哈希 + FromAddress *string // 來源地址 + ToAddress *string // 目標地址 + ChainFee *decimal.Decimal // 鏈上交易手續費 + ChainFeeCrypto *string // 鏈上手續費使用幣別 + Memo *string // 鏈上備註 + OrderNote *string // 訂單交易備註 +} diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go new file mode 100644 index 0000000..0bd0cbe --- /dev/null +++ b/internal/domain/wallet.go @@ -0,0 +1,19 @@ +package domain + +type WalletStatus int64 + +func (o *WalletStatus) ToInt() int { + return int(*o) +} + +type ThreePartyStatus int64 + +func (o *ThreePartyStatus) ToInt() int { + return int(*o) +} + +type DirectionType int64 + +func (o *DirectionType) ToInt() int { + return int(*o) +} diff --git a/internal/lib/mongo/custom_mongo_decimal.go b/internal/lib/mongo/custom_mongo_decimal.go new file mode 100644 index 0000000..6b56811 --- /dev/null +++ b/internal/lib/mongo/custom_mongo_decimal.go @@ -0,0 +1,52 @@ +package mongo + +import ( + "fmt" + "reflect" + + "github.com/shopspring/decimal" + "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/bson/bsonrw" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type MgoDecimal struct{} + +var ( + _ bsoncodec.ValueEncoder = &MgoDecimal{} + _ bsoncodec.ValueDecoder = &MgoDecimal{} +) + +func (dc *MgoDecimal) EncodeValue(_ bsoncodec.EncodeContext, w bsonrw.ValueWriter, value reflect.Value) error { + // TODO 待確認是否有非decimal.Decimal type而導致error的場景 + dec, ok := value.Interface().(decimal.Decimal) + if !ok { + return fmt.Errorf("value %v to encode is not of type decimal.Decimal", value) + } + + // Convert decimal.Decimal to primitive.Decimal128. + primDec, err := primitive.ParseDecimal128(dec.String()) + if err != nil { + return fmt.Errorf("converting decimal.Decimal %v to primitive.Decimal128 error: %w", dec, err) + } + + return w.WriteDecimal128(primDec) +} + +func (dc *MgoDecimal) DecodeValue(_ bsoncodec.DecodeContext, r bsonrw.ValueReader, value reflect.Value) error { + primDec, err := r.ReadDecimal128() + if err != nil { + return fmt.Errorf("reading primitive.Decimal128 from ValueReader error: %w", err) + } + + // Convert primitive.Decimal128 to decimal.Decimal. + dec, err := decimal.NewFromString(primDec.String()) + if err != nil { + return fmt.Errorf("converting primitive.Decimal128 %v to decimal.Decimal error: %w", primDec, err) + } + + // set as decimal.Decimal type + value.Set(reflect.ValueOf(dec)) + + return nil +} diff --git a/internal/lib/mongo/init_mongodb.go b/internal/lib/mongo/init_mongodb.go new file mode 100644 index 0000000..64f8315 --- /dev/null +++ b/internal/lib/mongo/init_mongodb.go @@ -0,0 +1,14 @@ +package mongo + +import ( + "app-cloudep-trade-service/internal/config" + "fmt" +) + +func MustMongoConnectURL(c config.Config) string { + return fmt.Sprintf("%s://%s:%s", + c.Mongo.Schema, + c.Mongo.Host, + c.Mongo.Port, + ) +} diff --git a/internal/lib/mongo/option.go b/internal/lib/mongo/option.go new file mode 100644 index 0000000..bcefdc6 --- /dev/null +++ b/internal/lib/mongo/option.go @@ -0,0 +1,51 @@ +package mongo + +import ( + "app-cloudep-trade-service/internal/config" + "reflect" + + "github.com/shopspring/decimal" + "github.com/zeromicro/go-zero/core/stores/mon" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type TypeCodec struct { + ValueType reflect.Type + Encoder bsoncodec.ValueEncoder + Decoder bsoncodec.ValueDecoder +} + +// WithTypeCodec registers TypeCodecs to convert custom types. +func WithTypeCodec(typeCodecs ...TypeCodec) mon.Option { + return func(c *options.ClientOptions) { + registry := bson.NewRegistry() + for _, v := range typeCodecs { + registry.RegisterTypeEncoder(v.ValueType, v.Encoder) + registry.RegisterTypeDecoder(v.ValueType, v.Decoder) + } + c.SetRegistry(registry) + } +} + +// SetCustomDecimalType force convert primitive.Decimal128 to decimal.Decimal. +func SetCustomDecimalType() mon.Option { + return WithTypeCodec(TypeCodec{ + ValueType: reflect.TypeOf(decimal.Decimal{}), + Encoder: &MgoDecimal{}, + Decoder: &MgoDecimal{}, + }) +} + +func InitMongoOptions(cfg config.Config) mon.Option { + return func(opts *options.ClientOptions) { + opts.SetMaxPoolSize(cfg.Mongo.MaxPoolSize) + opts.SetMinPoolSize(cfg.Mongo.MinPoolSize) + opts.SetMaxConnIdleTime(cfg.Mongo.MaxConnIdleTime) + opts.SetCompressors([]string{"snappy"}) + // opts.SetReplicaSet(cfg.Mongo.ReplicaName) + // opts.SetWriteConcern(writeconcern.W1()) + // opts.SetReadPreference(readpref.Primary()) + } +} diff --git a/internal/logic/couponservice/create_coupon_logic.go b/internal/logic/couponservice/create_coupon_logic.go new file mode 100644 index 0000000..b94d766 --- /dev/null +++ b/internal/logic/couponservice/create_coupon_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateCouponLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateCouponLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateCouponLogic { + return &CreateCouponLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CreateCoupon 創建優惠券 +func (l *CreateCouponLogic) CreateCoupon(in *trade.CreateCouponReq) (*trade.CouponResp, error) { + // todo: add your logic here and delete this line + + return &trade.CouponResp{}, nil +} diff --git a/internal/logic/couponservice/delete_coupon_logic.go b/internal/logic/couponservice/delete_coupon_logic.go new file mode 100644 index 0000000..704d354 --- /dev/null +++ b/internal/logic/couponservice/delete_coupon_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteCouponLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewDeleteCouponLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteCouponLogic { + return &DeleteCouponLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// DeleteCoupon 刪除優惠券 +func (l *DeleteCouponLogic) DeleteCoupon(in *trade.CouponQueryReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/couponservice/list_coupons_logic.go b/internal/logic/couponservice/list_coupons_logic.go new file mode 100644 index 0000000..595fbdb --- /dev/null +++ b/internal/logic/couponservice/list_coupons_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListCouponsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListCouponsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListCouponsLogic { + return &ListCouponsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ListCoupons列出所有優惠券 +func (l *ListCouponsLogic) ListCoupons(in *trade.CouponListReq) (*trade.CouponListResp, error) { + // todo: add your logic here and delete this line + + return &trade.CouponListResp{}, nil +} diff --git a/internal/logic/couponservice/query_coupon_logic.go b/internal/logic/couponservice/query_coupon_logic.go new file mode 100644 index 0000000..d38d884 --- /dev/null +++ b/internal/logic/couponservice/query_coupon_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryCouponLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewQueryCouponLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryCouponLogic { + return &QueryCouponLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// QueryCoupon查詢優惠券 +func (l *QueryCouponLogic) QueryCoupon(in *trade.CouponQueryReq) (*trade.CouponResp, error) { + // todo: add your logic here and delete this line + + return &trade.CouponResp{}, nil +} diff --git a/internal/logic/couponservice/update_coupon_logic.go b/internal/logic/couponservice/update_coupon_logic.go new file mode 100644 index 0000000..35cf418 --- /dev/null +++ b/internal/logic/couponservice/update_coupon_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateCouponLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUpdateCouponLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateCouponLogic { + return &UpdateCouponLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// UpdateCoupon更新優惠券 +func (l *UpdateCouponLogic) UpdateCoupon(in *trade.UpdateCouponReq) (*trade.CouponResp, error) { + // todo: add your logic here and delete this line + + return &trade.CouponResp{}, nil +} diff --git a/internal/logic/couponservice/validate_coupon_logic.go b/internal/logic/couponservice/validate_coupon_logic.go new file mode 100644 index 0000000..b592414 --- /dev/null +++ b/internal/logic/couponservice/validate_coupon_logic.go @@ -0,0 +1,31 @@ +package couponservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ValidateCouponLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewValidateCouponLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ValidateCouponLogic { + return &ValidateCouponLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ValidateCoupon 驗證優惠券 (在下單或支付時使用) +func (l *ValidateCouponLogic) ValidateCoupon(in *trade.ValidateCouponReq) (*trade.CouponValidationResp, error) { + // todo: add your logic here and delete this line + + return &trade.CouponValidationResp{}, nil +} diff --git a/internal/logic/inventoryservice/add_stock_logic.go b/internal/logic/inventoryservice/add_stock_logic.go new file mode 100644 index 0000000..ecea963 --- /dev/null +++ b/internal/logic/inventoryservice/add_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AddStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewAddStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddStockLogic { + return &AddStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// AddStock 增加庫存 +func (l *AddStockLogic) AddStock(in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/inventoryservice/adjust_stock_logic.go b/internal/logic/inventoryservice/adjust_stock_logic.go new file mode 100644 index 0000000..201cb92 --- /dev/null +++ b/internal/logic/inventoryservice/adjust_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdjustStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewAdjustStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdjustStockLogic { + return &AdjustStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// AdjustStock 調整庫存 (批量修改庫存,用於大批商品更新) +func (l *AdjustStockLogic) AdjustStock(in *trade.BatchStockAdjustmentReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/inventoryservice/create_stock_logic.go b/internal/logic/inventoryservice/create_stock_logic.go new file mode 100644 index 0000000..9ee754a --- /dev/null +++ b/internal/logic/inventoryservice/create_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateStockLogic { + return &CreateStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CreateStock 建立庫存品項 +func (l *CreateStockLogic) CreateStock(in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/inventoryservice/list_all_stock_logic.go b/internal/logic/inventoryservice/list_all_stock_logic.go new file mode 100644 index 0000000..7774e62 --- /dev/null +++ b/internal/logic/inventoryservice/list_all_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListAllStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListAllStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListAllStockLogic { + return &ListAllStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ListAllStock 查詢所有商品的庫存狀況 +func (l *ListAllStockLogic) ListAllStock(in *trade.StockListReq) (*trade.StockListResp, error) { + // todo: add your logic here and delete this line + + return &trade.StockListResp{}, nil +} diff --git a/internal/logic/inventoryservice/query_stock_logic.go b/internal/logic/inventoryservice/query_stock_logic.go new file mode 100644 index 0000000..6e08e27 --- /dev/null +++ b/internal/logic/inventoryservice/query_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewQueryStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryStockLogic { + return &QueryStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// QueryStock 查詢單一商品的庫存 +func (l *QueryStockLogic) QueryStock(in *trade.StockQueryReq) (*trade.StockResp, error) { + // todo: add your logic here and delete this line + + return &trade.StockResp{}, nil +} diff --git a/internal/logic/inventoryservice/reduce_stock_logic.go b/internal/logic/inventoryservice/reduce_stock_logic.go new file mode 100644 index 0000000..6c55b28 --- /dev/null +++ b/internal/logic/inventoryservice/reduce_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ReduceStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewReduceStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReduceStockLogic { + return &ReduceStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ReduceStock 減少庫存 +func (l *ReduceStockLogic) ReduceStock(in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/inventoryservice/release_reserved_stock_logic.go b/internal/logic/inventoryservice/release_reserved_stock_logic.go new file mode 100644 index 0000000..15c2cb9 --- /dev/null +++ b/internal/logic/inventoryservice/release_reserved_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ReleaseReservedStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewReleaseReservedStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReleaseReservedStockLogic { + return &ReleaseReservedStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ReleaseReservedStock 釋放預留庫存 (取消或失效的訂單) +func (l *ReleaseReservedStockLogic) ReleaseReservedStock(in *trade.StockReservationReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/inventoryservice/reserve_stock_logic.go b/internal/logic/inventoryservice/reserve_stock_logic.go new file mode 100644 index 0000000..51728aa --- /dev/null +++ b/internal/logic/inventoryservice/reserve_stock_logic.go @@ -0,0 +1,31 @@ +package inventoryservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ReserveStockLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewReserveStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReserveStockLogic { + return &ReserveStockLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ReserveStock 預留庫存 (用於未完成的訂單) +func (l *ReserveStockLogic) ReserveStock(in *trade.StockReservationReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/cancel_order_logic.go b/internal/logic/orderservice/cancel_order_logic.go new file mode 100644 index 0000000..cd499a6 --- /dev/null +++ b/internal/logic/orderservice/cancel_order_logic.go @@ -0,0 +1,56 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/internal/domain" + "app-cloudep-trade-service/internal/domain/usecase" + "context" + + ers "code.30cm.net/digimon/library-go/errs" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CancelOrderLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCancelOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelOrderLogic { + return &CancelOrderLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CancelOrderQuery 1.建單失敗 9.交易取消 10.交易異常 +type CancelOrderQuery struct { + BusinessID string `json:"business_id" validate:"required"` + Status int64 `json:"status" validate:"required,oneof=1 9 10"` +} + +// CancelOrder 取消訂單 +func (l *CancelOrderLogic) CancelOrder(in *trade.CancelOrderReq) (*trade.OKResp, error) { + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(&CancelOrderQuery{ + BusinessID: in.GetBusinessId(), + Status: in.GetStatus(), + }); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.OrderUseCase.CancelOrder(l.ctx, usecase.CancelOrderQuery{ + BusinessID: in.GetBusinessId(), + Status: domain.OrderStatus(in.GetStatus()), + }) + if err != nil { + return nil, err + } + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/create_order_logic.go b/internal/logic/orderservice/create_order_logic.go new file mode 100644 index 0000000..ad5f708 --- /dev/null +++ b/internal/logic/orderservice/create_order_logic.go @@ -0,0 +1,228 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/domain" + "app-cloudep-trade-service/internal/domain/usecase" + "app-cloudep-trade-service/internal/svc" + ers "code.30cm.net/digimon/library-go/errs" + "context" + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateOrderLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateOrderLogic { + return &CreateOrderLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type createOrderReq struct { // 訂單ID + BusinessID string `json:"business_id" validate:"required"` // 訂單業務流水號 + OrderType int8 `json:"order_type" validate:"required"` // 訂單類型 + OrderStatus int8 `json:"order_status" validate:"oneof=0 1 2 3 4 5 6 7 8 9 10 11"` // 訂單狀態 + Brand string `json:"brand" validate:"required"` // 下單平台 + OrderUID string `json:"order_uid" validate:"required"` // 下單用戶 UID + ReferenceID string `json:"reference_id" validate:"required"` // 訂單來源 + Count string `json:"count" validate:"required,decimalGt=0"` // 訂單數量 + OrderFee string `json:"order_fee" validate:"required,decimalGte=0"` // 訂單手續費 + Amount string `json:"amount" validate:"required,decimalGte=0"` // 單價 + ReferenceBrand *string `json:"reference_brand,omitempty" validate:"omitempty"` // 訂單來源平台 + ReferenceUID *string `json:"reference_uid,omitempty" validate:"omitempty"` // 訂單來源用戶 UID + WalletStatus *int64 `json:"wallet_status,omitempty" validate:"omitempty,oneof=1 2 3 4 5 6 7"` // 交易金額狀態 + ThreePartyStatus *int64 `json:"three_party_status,omitempty" validate:"omitempty,oneof=1 2 3"` // 三方請求狀態 + DirectionType *int64 `json:"direction_type,omitempty" validate:"omitempty,oneof=1 2"` // 交易方向 + CryptoType *string `json:"crypto_type,omitempty" validate:"omitempty"` // 交易幣種 + ThirdPartyFee *string `json:"third_party_fee,omitempty" validate:"omitempty,decimalGte=0"` // 第三方手續費 + CryptoToUSDTRate *string `json:"crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"` // 交易幣種對 USDT 匯率 + FiatToUSDRate *string `json:"fiat_to_usd_rate,omitempty" validate:"omitempty,decimalGte=0"` // 法幣對 USD 匯率 + FeeCryptoToUSDTRate *string `json:"fee_crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"` // 手續費幣種對 USDT 匯率 + USDTToCryptoTypeRate *string `json:"usdt_to_crypto_type_rate,omitempty" validate:"omitempty,decimalGte=0"` // USDT 對交易幣種匯率 + PaymentFiat *string `json:"payment_fiat,omitempty" validate:"omitempty"` // 支付法幣 + PaymentUnitPrice *string `json:"payment_unit_price,omitempty" validate:"omitempty,decimalGte=0"` // crypto 單價 + PaymentTemplateID *string `json:"payment_template_id,omitempty" validate:"omitempty"` // 支付方式配置 ID + OrderArrivalTime *int64 `json:"order_arrival_time,omitempty" validate:"omitempty,gt=1"` // 訂單到帳時間 + OrderPaymentTime *int64 `json:"order_payment_time,omitempty" validate:"omitempty,gt=1"` // 訂單付款時間 + UnpaidTimeoutSecond *int64 `json:"unpaid_timeout_second,omitempty" validate:"omitempty,gt=1"` // 支付期限秒數 + ChainType *string `json:"chain_type,omitempty" validate:"omitempty"` // 主網類型 + TxHash *string `json:"tx_hash,omitempty" validate:"omitempty"` // 交易哈希 + FromAddress *string `json:"from_address,omitempty" validate:"omitempty"` // 來源地址 + ToAddress *string `json:"to_address,omitempty" validate:"omitempty"` // 目標地址 + ChainFee *string `json:"chain_fee,omitempty" validate:"omitempty,decimalGte=0"` // 鏈上交易手續費 + ChainFeeCrypto *string `json:"chain_fee_crypto,omitempty" validate:"omitempty"` // 鏈上手續費使用幣別 + Memo *string `json:"memo,omitempty" validate:"omitempty"` // 鏈上備註 + OrderNote *string `json:"order_note,omitempty" validate:"omitempty"` +} + +//nolint:gocyclo,gocognit +func buildCreateOrderReq(in *trade.CreateOrderReq) (*createOrderReq, error) { + createOrderReq := &createOrderReq{ + BusinessID: in.BusinessId, + OrderType: int8(in.OrderType), + OrderStatus: int8(in.OrderStatus), + Brand: in.Brand, + OrderUID: in.OrderUid, + ReferenceID: in.ReferenceId, + } + + // 只有當 Count, OrderFee, Amount 不為空時才進行轉換和設置 + if in.Count != "" { + createOrderReq.Count = in.Count + } + if in.OrderFee != "" { + createOrderReq.OrderFee = in.OrderFee + } + if in.Amount != "" { + createOrderReq.Amount = in.Amount + } + + // 判斷可選字段,只有不為 nil 才設置 + if in.ReferenceBrand != nil { + createOrderReq.ReferenceBrand = in.ReferenceBrand + } + if in.ReferenceUid != nil { + createOrderReq.ReferenceUID = in.ReferenceUid + } + if in.WalletStatus != nil { + createOrderReq.WalletStatus = in.WalletStatus + } + if in.ThreePartyStatus != nil { + createOrderReq.ThreePartyStatus = in.ThreePartyStatus + } + if in.DirectionType != nil { + createOrderReq.DirectionType = in.DirectionType + } + if in.CryptoType != nil { + createOrderReq.CryptoType = in.CryptoType + } + if in.ThirdPartyFee != nil && *in.ThirdPartyFee != "" { + createOrderReq.ThirdPartyFee = in.ThirdPartyFee + } + if in.CryptoToUsdtRate != nil && *in.CryptoToUsdtRate != "" { + createOrderReq.CryptoToUSDTRate = in.CryptoToUsdtRate + } + if in.FiatToUsdRate != nil && *in.FiatToUsdRate != "" { + createOrderReq.FiatToUSDRate = in.FiatToUsdRate + } + if in.FeeCryptoToUsdtRate != nil && *in.FeeCryptoToUsdtRate != "" { + createOrderReq.FeeCryptoToUSDTRate = in.FeeCryptoToUsdtRate + } + if in.UsdtToCryptoTypeRate != nil && *in.UsdtToCryptoTypeRate != "" { + createOrderReq.USDTToCryptoTypeRate = in.UsdtToCryptoTypeRate + } + if in.PaymentFiat != nil { + createOrderReq.PaymentFiat = in.PaymentFiat + } + if in.PaymentUnitPrice != nil && *in.PaymentUnitPrice != "" { + createOrderReq.PaymentUnitPrice = in.PaymentUnitPrice + } + if in.PaymentTemplateId != nil { + createOrderReq.PaymentTemplateID = in.PaymentTemplateId + } + if in.OrderArrivalTime != nil { + createOrderReq.OrderArrivalTime = in.OrderArrivalTime + } + if in.OrderPaymentTime != nil { + createOrderReq.OrderPaymentTime = in.OrderPaymentTime + } + if in.UnpaidTimeoutSecond != nil { + createOrderReq.UnpaidTimeoutSecond = in.UnpaidTimeoutSecond + } + if in.ChainType != nil { + createOrderReq.ChainType = in.ChainType + } + if in.TxHash != nil { + createOrderReq.TxHash = in.TxHash + } + if in.FromAddress != nil { + createOrderReq.FromAddress = in.FromAddress + } + if in.ToAddress != nil { + createOrderReq.ToAddress = in.ToAddress + } + if in.ChainFee != nil && *in.ChainFee != "" { + createOrderReq.ChainFee = in.ChainFee + } + if in.ChainFeeCrypto != nil { + createOrderReq.ChainFeeCrypto = in.ChainFeeCrypto + } + if in.Memo != nil { + createOrderReq.Memo = in.Memo + } + if in.OrderNote != nil { + createOrderReq.OrderNote = in.OrderNote + } + + return createOrderReq, nil +} + +// toCreateOrderReq 將 createOrderReq 轉換為 CreateOrderReq +// toCreateOrderUseCase 將 createOrderReq 轉換為 CreateOrderReq +func toCreateOrderUseCase(req *createOrderReq) usecase.CreateOrderReq { + return usecase.CreateOrderReq{ + BusinessID: req.BusinessID, + OrderType: domain.OrderType(req.OrderType), + OrderStatus: domain.OrderStatus(req.OrderStatus), + Brand: req.Brand, + OrderUID: req.OrderUID, + ReferenceID: req.ReferenceID, + Count: *stringToDecimalPtr(&req.Count), + OrderFee: *stringToDecimalPtr(&req.OrderFee), + Amount: *stringToDecimalPtr(&req.Amount), + WalletStatus: getInt64Value(req.WalletStatus), + DirectionType: getInt64Value(req.DirectionType), + ReferenceBrand: req.ReferenceBrand, + ReferenceUID: req.ReferenceUID, + ThreePartyStatus: req.ThreePartyStatus, + CryptoType: req.CryptoType, + ThirdPartyFee: stringToDecimalPtr(req.ThirdPartyFee), + CryptoToUSDTRate: stringToDecimalPtr(req.CryptoToUSDTRate), + FiatToUSDRate: stringToDecimalPtr(req.FiatToUSDRate), + FeeCryptoToUSDTRate: stringToDecimalPtr(req.FeeCryptoToUSDTRate), + USDTToCryptoTypeRate: stringToDecimalPtr(req.USDTToCryptoTypeRate), + PaymentFiat: req.PaymentFiat, + PaymentUnitPrice: stringToDecimalPtr(req.PaymentUnitPrice), + PaymentTemplateID: req.PaymentTemplateID, + OrderArrivalTime: req.OrderArrivalTime, + OrderPaymentTime: req.OrderPaymentTime, + UnpaidTimeoutSecond: req.UnpaidTimeoutSecond, + ChainType: req.ChainType, + TxHash: req.TxHash, + FromAddress: req.FromAddress, + ToAddress: req.ToAddress, + ChainFee: stringToDecimalPtr(req.ChainFee), + ChainFeeCrypto: req.ChainFeeCrypto, + Memo: req.Memo, + OrderNote: req.OrderNote, + } +} + +// CreateOrder 建立訂單 +func (l *CreateOrderLogic) CreateOrder(in *trade.CreateOrderReq) (*trade.OKResp, error) { + + req, err := buildCreateOrderReq(in) + if err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(req); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + err = l.svcCtx.OrderUseCase.CreateOrder(l.ctx, toCreateOrderUseCase(req)) + if err != nil { + return nil, err + } + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/delete_order_logic.go b/internal/logic/orderservice/delete_order_logic.go new file mode 100644 index 0000000..2b804e5 --- /dev/null +++ b/internal/logic/orderservice/delete_order_logic.go @@ -0,0 +1,52 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/internal/domain/usecase" + "context" + + ers "code.30cm.net/digimon/library-go/errs" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteOrderLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewDeleteOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteOrderLogic { + return &DeleteOrderLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// DeleteOrderQuery 刪除訂單(軟刪除) +type DeleteOrderQuery struct { + BusinessID string `json:"business_id" validate:"required"` +} + +// DeleteOrder 刪除訂單(軟刪除) +func (l *DeleteOrderLogic) DeleteOrder(in *trade.DeleteOrderReq) (*trade.OKResp, error) { + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(&DeleteOrderQuery{ + BusinessID: in.GetBusinessId(), + }); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.OrderUseCase.DeleteOrder(l.ctx, usecase.DeleteOrderQuery{ + BusinessID: in.GetBusinessId(), + }) + if err != nil { + return nil, err + } + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/get_order_logic.go b/internal/logic/orderservice/get_order_logic.go new file mode 100644 index 0000000..967fa81 --- /dev/null +++ b/internal/logic/orderservice/get_order_logic.go @@ -0,0 +1,89 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/internal/domain/usecase" + "context" + + ers "code.30cm.net/digimon/library-go/errs" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetOrderLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrderLogic { + return &GetOrderLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type GetOrderQuery struct { + BusinessID string `json:"business_id" validate:"required"` +} + +// GetOrder 取得訂單詳情 +func (l *GetOrderLogic) GetOrder(in *trade.GetOrderReq) (*trade.GetOrderResp, error) { + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(&GetOrderQuery{ + BusinessID: in.GetBusinessId(), + }); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + o, err := l.svcCtx.OrderUseCase.GetOrder(l.ctx, usecase.GetOrderQuery{ + BusinessID: in.GetBusinessId(), + }) + if err != nil { + return nil, err + } + + return &trade.GetOrderResp{ + UpdateTime: o.UpdateTime, + CreateTime: o.CreateTime, + BusinessId: o.BusinessID, + OrderType: int32(o.OrderType), + OrderStatus: int32(o.OrderStatus), + Brand: o.Brand, + OrderUid: o.OrderUID, + ReferenceId: o.ReferenceID, + Count: o.Count, + OrderFee: o.OrderFee, + Amount: o.Amount, + // 下面的為未來擴充用的欄位 + ReferenceBrand: o.ReferenceBrand, + ReferenceUid: o.ReferenceUID, + WalletStatus: o.WalletStatus, + ThreePartyStatus: o.ThreePartyStatus, + DirectionType: o.DirectionType, + CryptoType: o.CryptoType, + ThirdPartyFee: o.ThirdPartyFee, + CryptoToUsdtRate: o.CryptoToUsdtRate, + FiatToUsdRate: o.FiatToUsdRate, + FeeCryptoToUsdtRate: o.FeeCryptoToUsdtRate, + UsdtToCryptoTypeRate: o.UsdtToCryptoTypeRate, + PaymentFiat: o.PaymentFiat, + PaymentUnitPrice: o.PaymentUnitPrice, + PaymentTemplateId: o.PaymentTemplateID, + OrderArrivalTime: o.OrderArrivalTime, + OrderPaymentTime: o.OrderPaymentTime, + UnpaidTimeoutSecond: o.UnpaidTimeoutSecond, + ChainType: o.ChainType, + TxHash: o.TxHash, + FromAddress: o.FromAddress, + ToAddress: o.ToAddress, + ChainFee: o.ChainFee, + ChainFeeCrypto: o.ChainFeeCrypto, + Memo: o.Memo, + OrderNote: o.OrderNote, + }, nil +} diff --git a/internal/logic/orderservice/list_order_logic.go b/internal/logic/orderservice/list_order_logic.go new file mode 100644 index 0000000..b68bfcd --- /dev/null +++ b/internal/logic/orderservice/list_order_logic.go @@ -0,0 +1,158 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/internal/domain" + "app-cloudep-trade-service/internal/domain/usecase" + "context" + + ers "code.30cm.net/digimon/library-go/errs" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListOrderLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListOrderLogic { + return &ListOrderLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type GetOrderListReq struct { + PageIndex int64 `json:"page_index" validate:"required"` + PageSize int64 `json:"page_size" validate:"required"` + + ReferenceID string `json:"reference_id"` + ReferenceUID string `json:"reference_uid"` + BusinessID string `json:"business_id"` + UID string `json:"uid"` + OrderType int `json:"order_type"` + DirectionType []int64 `json:"direction_type"` + OrderStatus []int64 `json:"order_status"` + + StartCreateTime int64 `json:"start_create_time"` + EndCreateTime int64 `json:"end_create_time"` + StartUpdateTime int64 `json:"start_update_time"` + EndUpdateTime int64 `json:"end_update_time"` + StartOrderArrivalTime int64 `json:"start_order_arrival_time"` + EndOrderArrivalTime int64 `json:"end_order_arrival_time"` + StartOrderPaymentTime int64 `json:"start_order_payment_time"` + EndOrderPaymentTime int64 `json:"end_order_payment_time"` + + CryptoType string `json:"crypto_type"` + TxHash string `json:"tx_hash"` +} + +// toGetOrderListReq 將 JSON 標籤結構轉換為無 JSON 標籤結構 +func toGetOrderListReq(req *trade.ListOrderReq) usecase.GetOrderListReq { + return usecase.GetOrderListReq{ + PageIndex: req.PageIndex, + PageSize: req.PageSize, + ReferenceID: req.ReferenceId, + ReferenceUID: req.ReferenceUid, + BusinessID: req.BusinessId, + UID: req.Uid, + OrderType: domain.OrderType(req.OrderType), + DirectionType: i32To64(req.DirectionType), + OrderStatus: i32To64(req.OrderStatus), + StartCreateTime: req.StartCreateTime, + EndCreateTime: req.EndCreateTime, + StartUpdateTime: req.StartUpdateTime, + EndUpdateTime: req.EndUpdateTime, + StartOrderArrivalTime: req.StartOrderArrivalTime, + EndOrderArrivalTime: req.EndOrderArrivalTime, + StartOrderPaymentTime: req.StartOrderPaymentTime, + EndOrderPaymentTime: req.EndOrderPaymentTime, + CryptoType: req.CryptoType, + TxHash: req.TxHash, + } +} + +func i32To64(i []int32) []int64 { + if len(i) == 0 { + return nil + } + result := make([]int64, 0, len(i)) + for item := range i { + result = append(result, int64(item)) + } + + return result +} + +// ListOrder 取得訂單列表 +func (l *ListOrderLogic) ListOrder(in *trade.ListOrderReq) (*trade.ListOrderResp, error) { + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(&GetOrderListReq{ + PageIndex: in.GetPageIndex(), + PageSize: in.GetPageSize(), + }); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + order, err := l.svcCtx.OrderUseCase.ListOrder(l.ctx, toGetOrderListReq(in)) + if err != nil { + return nil, err + } + + data := make([]*trade.GetOrderResp, 0, len(order.Data)) + for _, item := range order.Data { + data = append(data, &trade.GetOrderResp{ + BusinessId: item.BusinessID, + OrderType: int32(item.OrderType), + OrderStatus: int32(item.OrderStatus), + Brand: item.Brand, + OrderUid: item.OrderUID, + ReferenceId: item.ReferenceID, + Count: item.Count, + OrderFee: item.OrderFee, + Amount: item.Amount, + ReferenceBrand: item.ReferenceBrand, + ReferenceUid: item.ReferenceUID, + WalletStatus: item.WalletStatus, + ThreePartyStatus: item.ThreePartyStatus, + DirectionType: item.DirectionType, + CryptoType: item.CryptoType, + ThirdPartyFee: item.ThirdPartyFee, + CryptoToUsdtRate: item.CryptoToUsdtRate, + FiatToUsdRate: item.FiatToUsdRate, + FeeCryptoToUsdtRate: item.FeeCryptoToUsdtRate, + UsdtToCryptoTypeRate: item.UsdtToCryptoTypeRate, + PaymentFiat: item.PaymentFiat, + PaymentUnitPrice: item.PaymentUnitPrice, + PaymentTemplateId: item.PaymentTemplateID, + OrderArrivalTime: item.OrderArrivalTime, + OrderPaymentTime: item.OrderPaymentTime, + UnpaidTimeoutSecond: item.UnpaidTimeoutSecond, + ChainType: item.ChainType, + TxHash: item.TxHash, + FromAddress: item.FromAddress, + ToAddress: item.ToAddress, + ChainFee: item.ChainFee, + ChainFeeCrypto: item.ChainFeeCrypto, + Memo: item.Memo, + OrderNote: item.OrderNote, + CreateTime: item.CreateTime, + UpdateTime: item.UpdateTime, + }) + } + + return &trade.ListOrderResp{ + Data: data, + Page: &trade.Pager{ + Index: order.Page.Index, + Size: order.Page.Size, + Total: order.Page.Total, + }, + }, nil +} diff --git a/internal/logic/orderservice/modify_order_status_logic.go b/internal/logic/orderservice/modify_order_status_logic.go new file mode 100644 index 0000000..47c6b1e --- /dev/null +++ b/internal/logic/orderservice/modify_order_status_logic.go @@ -0,0 +1,59 @@ +package orderservicelogic + +import ( + "app-cloudep-trade-service/internal/domain/usecase" + "context" + + ers "code.30cm.net/digimon/library-go/errs" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ModifyOrderStatusLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewModifyOrderStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyOrderStatusLogic { + return &ModifyOrderStatusLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ModifyOrderQuery +// 0.建立訂單 1.建單失敗 2.審核中 3.付款中 4.已付款 +// 5.已付款待轉帳 6.申訴中 7.交易完成 +// 8.交易失敗 9.交易取消 10.交易異常 11.交易超時 + +type ModifyOrderQuery struct { + BusinessID string `json:"business_id" validate:"required"` + Status int64 `json:"status" validate:"required,oneof=2 3 4 5 6 7 8 11"` +} + +// ModifyOrderStatus 修改訂單狀態 +func (l *ModifyOrderStatusLogic) ModifyOrderStatus(in *trade.ModifyOrderStatusReq) (*trade.OKResp, error) { + // 驗證資料 + if err := l.svcCtx.Validate.ValidateAll(&ModifyOrderQuery{ + BusinessID: in.GetBusinessId(), + Status: in.GetStatus(), + }); err != nil { + // 錯誤代碼 06-011-00 + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.OrderUseCase.ModifyOrderStatus(l.ctx, &usecase.ModifyOrderQuery{ + Status: in.GetStatus(), + BusinessID: in.GetBusinessId(), + }) + if err != nil { + return nil, err + } + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/order_status_timeout_logic.go b/internal/logic/orderservice/order_status_timeout_logic.go new file mode 100644 index 0000000..ae15f9c --- /dev/null +++ b/internal/logic/orderservice/order_status_timeout_logic.go @@ -0,0 +1,34 @@ +package orderservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type OrderStatusTimeoutLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewOrderStatusTimeoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrderStatusTimeoutLogic { + return &OrderStatusTimeoutLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// OrderStatusTimeout 訂單超時任務/cron/order-status/timeout +func (l *OrderStatusTimeoutLogic) OrderStatusTimeout(_ *trade.OrderStatusTimeoutReq) (*trade.OKResp, error) { + err := l.svcCtx.OrderUseCase.OrderStatusTimeout(l.ctx) + if err != nil { + return nil, err + } + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/orderservice/utils.go b/internal/logic/orderservice/utils.go new file mode 100644 index 0000000..29be83c --- /dev/null +++ b/internal/logic/orderservice/utils.go @@ -0,0 +1,42 @@ +package orderservicelogic + +import ( + "github.com/shopspring/decimal" + "github.com/zeromicro/go-zero/core/logx" +) + +func decimalPtrFromString(val string) *decimal.Decimal { + if val == "" { + return nil + } + dec, err := decimal.NewFromString(val) + if err != nil { + logx.Errorf("Failed to convert string to decimal: %v", err) + + return nil + } + + return &dec +} + +// getInt64Value 將 *int64 的值返回,如果為 nil 則返回 0 +func getInt64Value(val *int64) int64 { + if val == nil { + return 0 + } + return *val +} + +// stringToDecimalPtr 將 *string 轉換為 *decimal.Decimal,如果解析失敗或值為 nil 則返回 nil +func stringToDecimalPtr(s *string) *decimal.Decimal { + if s == nil { + return nil + } + + dec, err := decimal.NewFromString(*s) + if err != nil { + return nil // 解析失敗時返回 nil,或根據需求記錄錯誤 + } + + return &dec +} diff --git a/internal/logic/productservice/create_product_logic.go b/internal/logic/productservice/create_product_logic.go new file mode 100644 index 0000000..02ac737 --- /dev/null +++ b/internal/logic/productservice/create_product_logic.go @@ -0,0 +1,31 @@ +package productservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateProductLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductLogic { + return &CreateProductLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CreateProduct 新增商品 +func (l *CreateProductLogic) CreateProduct(in *trade.ProductCreateReq) (*trade.ProductResp, error) { + // todo: add your logic here and delete this line + + return &trade.ProductResp{}, nil +} diff --git a/internal/logic/productservice/delete_product_logic.go b/internal/logic/productservice/delete_product_logic.go new file mode 100644 index 0000000..cecbcf4 --- /dev/null +++ b/internal/logic/productservice/delete_product_logic.go @@ -0,0 +1,31 @@ +package productservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteProductLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteProductLogic { + return &DeleteProductLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// DeleteProduct 刪除商品 +func (l *DeleteProductLogic) DeleteProduct(in *trade.ProductDeleteReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/productservice/list_all_products_logic.go b/internal/logic/productservice/list_all_products_logic.go new file mode 100644 index 0000000..0739982 --- /dev/null +++ b/internal/logic/productservice/list_all_products_logic.go @@ -0,0 +1,31 @@ +package productservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListAllProductsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListAllProductsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListAllProductsLogic { + return &ListAllProductsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ListAllProducts 查詢所有商品資訊 +func (l *ListAllProductsLogic) ListAllProducts(in *trade.ProductListReq) (*trade.ProductListResp, error) { + // todo: add your logic here and delete this line + + return &trade.ProductListResp{}, nil +} diff --git a/internal/logic/productservice/query_product_logic.go b/internal/logic/productservice/query_product_logic.go new file mode 100644 index 0000000..cdf6cfe --- /dev/null +++ b/internal/logic/productservice/query_product_logic.go @@ -0,0 +1,31 @@ +package productservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryProductLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewQueryProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryProductLogic { + return &QueryProductLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// QueryProduct 查詢單一商品資訊 +func (l *QueryProductLogic) QueryProduct(in *trade.ProductQueryReq) (*trade.ProductResp, error) { + // todo: add your logic here and delete this line + + return &trade.ProductResp{}, nil +} diff --git a/internal/logic/productservice/update_product_logic.go b/internal/logic/productservice/update_product_logic.go new file mode 100644 index 0000000..735c420 --- /dev/null +++ b/internal/logic/productservice/update_product_logic.go @@ -0,0 +1,31 @@ +package productservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateProductLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUpdateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProductLogic { + return &UpdateProductLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// UpdateProduct 更新商品資訊 +func (l *UpdateProductLogic) UpdateProduct(in *trade.ProductUpdateReq) (*trade.ProductResp, error) { + // todo: add your logic here and delete this line + + return &trade.ProductResp{}, nil +} diff --git a/internal/logic/subscriptionservice/cancel_subscription_logic.go b/internal/logic/subscriptionservice/cancel_subscription_logic.go new file mode 100644 index 0000000..7927d47 --- /dev/null +++ b/internal/logic/subscriptionservice/cancel_subscription_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CancelSubscriptionLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCancelSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelSubscriptionLogic { + return &CancelSubscriptionLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CancelSubscription 取消訂閱 +func (l *CancelSubscriptionLogic) CancelSubscription(in *trade.SubscriptionCancelReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/subscriptionservice/check_subscription_status_logic.go b/internal/logic/subscriptionservice/check_subscription_status_logic.go new file mode 100644 index 0000000..d9d520d --- /dev/null +++ b/internal/logic/subscriptionservice/check_subscription_status_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CheckSubscriptionStatusLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCheckSubscriptionStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckSubscriptionStatusLogic { + return &CheckSubscriptionStatusLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CheckSubscriptionStatus 查詢訂閱狀態 (啟用/過期/取消) +func (l *CheckSubscriptionStatusLogic) CheckSubscriptionStatus(in *trade.SubscriptionStatusQueryReq) (*trade.SubscriptionStatusResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionStatusResp{}, nil +} diff --git a/internal/logic/subscriptionservice/create_subscription_logic.go b/internal/logic/subscriptionservice/create_subscription_logic.go new file mode 100644 index 0000000..7dbee9e --- /dev/null +++ b/internal/logic/subscriptionservice/create_subscription_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateSubscriptionLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateSubscriptionLogic { + return &CreateSubscriptionLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CreateSubscription 建立訂閱 +func (l *CreateSubscriptionLogic) CreateSubscription(in *trade.SubscriptionCreateReq) (*trade.SubscriptionResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionResp{}, nil +} diff --git a/internal/logic/subscriptionservice/list_subscriptions_logic.go b/internal/logic/subscriptionservice/list_subscriptions_logic.go new file mode 100644 index 0000000..8fc57b3 --- /dev/null +++ b/internal/logic/subscriptionservice/list_subscriptions_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListSubscriptionsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListSubscriptionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListSubscriptionsLogic { + return &ListSubscriptionsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ListSubscriptions 查詢所有訂閱 (用於管理和監控) +func (l *ListSubscriptionsLogic) ListSubscriptions(in *trade.ListSubscriptionsReq) (*trade.SubscriptionListResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionListResp{}, nil +} diff --git a/internal/logic/subscriptionservice/query_subscription_logic.go b/internal/logic/subscriptionservice/query_subscription_logic.go new file mode 100644 index 0000000..ead2424 --- /dev/null +++ b/internal/logic/subscriptionservice/query_subscription_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QuerySubscriptionLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewQuerySubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QuerySubscriptionLogic { + return &QuerySubscriptionLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// QuerySubscription 查詢單一訂閱資訊 +func (l *QuerySubscriptionLogic) QuerySubscription(in *trade.SubscriptionQueryReq) (*trade.SubscriptionResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionResp{}, nil +} diff --git a/internal/logic/subscriptionservice/refresh_subscription_status_logic.go b/internal/logic/subscriptionservice/refresh_subscription_status_logic.go new file mode 100644 index 0000000..6294f0c --- /dev/null +++ b/internal/logic/subscriptionservice/refresh_subscription_status_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type RefreshSubscriptionStatusLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewRefreshSubscriptionStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshSubscriptionStatusLogic { + return &RefreshSubscriptionStatusLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// RefreshSubscriptionStatus cron 改變訂閱的狀態(時間到了要過期,需要續約自動續約),每 5 分鐘執行一次 +func (l *RefreshSubscriptionStatusLogic) RefreshSubscriptionStatus(in *trade.NoneReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/subscriptionservice/renew_subscription_logic.go b/internal/logic/subscriptionservice/renew_subscription_logic.go new file mode 100644 index 0000000..c06e628 --- /dev/null +++ b/internal/logic/subscriptionservice/renew_subscription_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type RenewSubscriptionLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewRenewSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RenewSubscriptionLogic { + return &RenewSubscriptionLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// RenewSubscription 續訂訂閱 +func (l *RenewSubscriptionLogic) RenewSubscription(in *trade.SubscriptionRenewReq) (*trade.SubscriptionResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionResp{}, nil +} diff --git a/internal/logic/subscriptionservice/update_subscription_logic.go b/internal/logic/subscriptionservice/update_subscription_logic.go new file mode 100644 index 0000000..2e76a2e --- /dev/null +++ b/internal/logic/subscriptionservice/update_subscription_logic.go @@ -0,0 +1,31 @@ +package subscriptionservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateSubscriptionLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUpdateSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateSubscriptionLogic { + return &UpdateSubscriptionLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// UpdateSubscription 更新訂閱設定 +func (l *UpdateSubscriptionLogic) UpdateSubscription(in *trade.SubscriptionUpdateReq) (*trade.SubscriptionResp, error) { + // todo: add your logic here and delete this line + + return &trade.SubscriptionResp{}, nil +} diff --git a/internal/logic/walletservice/append_to_freeze_logic.go b/internal/logic/walletservice/append_to_freeze_logic.go new file mode 100644 index 0000000..b28f9ac --- /dev/null +++ b/internal/logic/walletservice/append_to_freeze_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AppendToFreezeLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewAppendToFreezeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AppendToFreezeLogic { + return &AppendToFreezeLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// AppendToFreeze 追加凍結 - 減少可用餘額,追加到現有的 [凍結餘額] (基於原order_id) +func (l *AppendToFreezeLogic) AppendToFreeze(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/cancel_freeze_logic.go b/internal/logic/walletservice/cancel_freeze_logic.go new file mode 100644 index 0000000..4328343 --- /dev/null +++ b/internal/logic/walletservice/cancel_freeze_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CancelFreezeLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCancelFreezeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelFreezeLogic { + return &CancelFreezeLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CancelFreeze 取消凍結 - 減少 [凍結餘額],加回 [可用餘額],可指定金額 +func (l *CancelFreezeLogic) CancelFreeze(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/create_wallet_logic.go b/internal/logic/walletservice/create_wallet_logic.go new file mode 100644 index 0000000..105c151 --- /dev/null +++ b/internal/logic/walletservice/create_wallet_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateWalletLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateWalletLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateWalletLogic { + return &CreateWalletLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// CreateWallet 建立錢包 +func (l *CreateWalletLogic) CreateWallet(in *trade.CreateWalletReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/deposit_funds_logic.go b/internal/logic/walletservice/deposit_funds_logic.go new file mode 100644 index 0000000..bbf3202 --- /dev/null +++ b/internal/logic/walletservice/deposit_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DepositFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewDepositFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DepositFundsLogic { + return &DepositFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// DepositFunds 充值 - 增加 [可用餘額] 或 [限制餘額] +func (l *DepositFundsLogic) DepositFunds(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/freeze_funds_logic.go b/internal/logic/walletservice/freeze_funds_logic.go new file mode 100644 index 0000000..1927ef8 --- /dev/null +++ b/internal/logic/walletservice/freeze_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type FreezeFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewFreezeFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FreezeFundsLogic { + return &FreezeFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// FreezeFunds 凍結 - 將 [可用餘額] 減少,加到 [凍結餘額] +func (l *FreezeFundsLogic) FreezeFunds(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/query_balance_logic.go b/internal/logic/walletservice/query_balance_logic.go new file mode 100644 index 0000000..5b56e13 --- /dev/null +++ b/internal/logic/walletservice/query_balance_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryBalanceLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewQueryBalanceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryBalanceLogic { + return &QueryBalanceLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// QueryBalance 餘額查詢 -> 依照日期查詢 +func (l *QueryBalanceLogic) QueryBalance(in *trade.QueryBalanceByDateReq) (*trade.QueryBalanceByDateResp, error) { + // todo: add your logic here and delete this line + + return &trade.QueryBalanceByDateResp{}, nil +} diff --git a/internal/logic/walletservice/rollback_freeze_logic.go b/internal/logic/walletservice/rollback_freeze_logic.go new file mode 100644 index 0000000..b0d6158 --- /dev/null +++ b/internal/logic/walletservice/rollback_freeze_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type RollbackFreezeLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewRollbackFreezeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RollbackFreezeLogic { + return &RollbackFreezeLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// RollbackFreeze 凍結回滾 - 減少 [凍結餘額],加回 [可用餘額],不可指定金額,整筆訂單的凍結金額加回 [可用餘額] +func (l *RollbackFreezeLogic) RollbackFreeze(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/settle_restricted_funds_logic.go b/internal/logic/walletservice/settle_restricted_funds_logic.go new file mode 100644 index 0000000..5de959f --- /dev/null +++ b/internal/logic/walletservice/settle_restricted_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type SettleRestrictedFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewSettleRestrictedFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SettleRestrictedFundsLogic { + return &SettleRestrictedFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// SettleRestrictedFunds 結算限制餘額 - 限制餘額在 T+N 天後自動失效,每日只能執行一次 +func (l *SettleRestrictedFundsLogic) SettleRestrictedFunds(in *trade.NoneReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/transfer_to_restricted_funds_logic.go b/internal/logic/walletservice/transfer_to_restricted_funds_logic.go new file mode 100644 index 0000000..977991b --- /dev/null +++ b/internal/logic/walletservice/transfer_to_restricted_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type TransferToRestrictedFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewTransferToRestrictedFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TransferToRestrictedFundsLogic { + return &TransferToRestrictedFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// TransferToRestrictedFunds 限制 - 減少 [凍結餘額],轉移到另一個使用者的 [限制餘額] +func (l *TransferToRestrictedFundsLogic) TransferToRestrictedFunds(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/unfreeze_funds_logic.go b/internal/logic/walletservice/unfreeze_funds_logic.go new file mode 100644 index 0000000..4d31cbf --- /dev/null +++ b/internal/logic/walletservice/unfreeze_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UnfreezeFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUnfreezeFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnfreezeFundsLogic { + return &UnfreezeFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// UnfreezeFunds 解凍 - 減少 [凍結餘額] +func (l *UnfreezeFundsLogic) UnfreezeFunds(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/logic/walletservice/withdraw_funds_logic.go b/internal/logic/walletservice/withdraw_funds_logic.go new file mode 100644 index 0000000..543284c --- /dev/null +++ b/internal/logic/walletservice/withdraw_funds_logic.go @@ -0,0 +1,31 @@ +package walletservicelogic + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type WithdrawFundsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewWithdrawFundsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WithdrawFundsLogic { + return &WithdrawFundsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// WithdrawFunds 提現 - 減少 [可用餘額] +func (l *WithdrawFundsLogic) WithdrawFunds(in *trade.WalletTransactionReq) (*trade.OKResp, error) { + // todo: add your logic here and delete this line + + return &trade.OKResp{}, nil +} diff --git a/internal/mock/lib/validate.go b/internal/mock/lib/validate.go new file mode 100644 index 0000000..dd852f8 --- /dev/null +++ b/internal/mock/lib/validate.go @@ -0,0 +1,73 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./validate.go +// +// Generated by this command: +// +// mockgen -source=./validate.go -destination=../../mock/lib/validate.go -package=lib +// + +// Package lib is a generated GoMock package. +package lib + +import ( + reflect "reflect" + + required "code.30cm.net/digimon/library-go/validator" + + gomock "go.uber.org/mock/gomock" +) + +// MockValidate is a mock of Validate interface. +type MockValidate struct { + ctrl *gomock.Controller + recorder *MockValidateMockRecorder +} + +// MockValidateMockRecorder is the mock recorder for MockValidate. +type MockValidateMockRecorder struct { + mock *MockValidate +} + +// NewMockValidate creates a new mock instance. +func NewMockValidate(ctrl *gomock.Controller) *MockValidate { + mock := &MockValidate{ctrl: ctrl} + mock.recorder = &MockValidateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidate) EXPECT() *MockValidateMockRecorder { + return m.recorder +} + +// BindToValidator mocks base method. +func (m *MockValidate) BindToValidator(opts ...required.Option) error { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BindToValidator", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// BindToValidator indicates an expected call of BindToValidator. +func (mr *MockValidateMockRecorder) BindToValidator(opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindToValidator", reflect.TypeOf((*MockValidate)(nil).BindToValidator), opts...) +} + +// ValidateAll mocks base method. +func (m *MockValidate) ValidateAll(obj any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateAll", obj) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateAll indicates an expected call of ValidateAll. +func (mr *MockValidateMockRecorder) ValidateAll(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAll", reflect.TypeOf((*MockValidate)(nil).ValidateAll), obj) +} diff --git a/internal/mock/model/order_model.go b/internal/mock/model/order_model.go new file mode 100644 index 0000000..0d22ccb --- /dev/null +++ b/internal/mock/model/order_model.go @@ -0,0 +1,177 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/model/mongo/order_model.go +// +// Generated by this command: +// +// mockgen -source=./internal/model/mongo/order_model.go -destination=./internal/mock/model/order_model.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + mongo "app-cloudep-trade-service/internal/model/mongo" + context "context" + reflect "reflect" + + mongo0 "go.mongodb.org/mongo-driver/mongo" + gomock "go.uber.org/mock/gomock" +) + +// MockOrderModel is a mock of OrderModel interface. +type MockOrderModel struct { + ctrl *gomock.Controller + recorder *MockOrderModelMockRecorder +} + +// MockOrderModelMockRecorder is the mock recorder for MockOrderModel. +type MockOrderModelMockRecorder struct { + mock *MockOrderModel +} + +// NewMockOrderModel creates a new mock instance. +func NewMockOrderModel(ctrl *gomock.Controller) *MockOrderModel { + mock := &MockOrderModel{ctrl: ctrl} + mock.recorder = &MockOrderModelMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOrderModel) EXPECT() *MockOrderModelMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockOrderModel) Delete(ctx context.Context, id string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, id) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delete indicates an expected call of Delete. +func (mr *MockOrderModelMockRecorder) Delete(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockOrderModel)(nil).Delete), ctx, id) +} + +// DeleteByBusinessID mocks base method. +func (m *MockOrderModel) DeleteByBusinessID(ctx context.Context, id string) (*mongo0.UpdateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteByBusinessID", ctx, id) + ret0, _ := ret[0].(*mongo0.UpdateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteByBusinessID indicates an expected call of DeleteByBusinessID. +func (mr *MockOrderModelMockRecorder) DeleteByBusinessID(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByBusinessID", reflect.TypeOf((*MockOrderModel)(nil).DeleteByBusinessID), ctx, id) +} + +// FindOne mocks base method. +func (m *MockOrderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindOne", ctx, id) + ret0, _ := ret[0].(*mongo.Order) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindOne indicates an expected call of FindOne. +func (mr *MockOrderModelMockRecorder) FindOne(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockOrderModel)(nil).FindOne), ctx, id) +} + +// FindOneBusinessID mocks base method. +func (m *MockOrderModel) FindOneBusinessID(ctx context.Context, id string) (*mongo.Order, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindOneBusinessID", ctx, id) + ret0, _ := ret[0].(*mongo.Order) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindOneBusinessID indicates an expected call of FindOneBusinessID. +func (mr *MockOrderModelMockRecorder) FindOneBusinessID(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneBusinessID", reflect.TypeOf((*MockOrderModel)(nil).FindOneBusinessID), ctx, id) +} + +// Insert mocks base method. +func (m *MockOrderModel) Insert(ctx context.Context, data *mongo.Order) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Insert", ctx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert. +func (mr *MockOrderModelMockRecorder) Insert(ctx, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockOrderModel)(nil).Insert), ctx, data) +} + +// ListOrder mocks base method. +func (m *MockOrderModel) ListOrder(ctx context.Context, req mongo.GetOrderListReq) ([]mongo.Order, int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListOrder", ctx, req) + ret0, _ := ret[0].([]mongo.Order) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListOrder indicates an expected call of ListOrder. +func (mr *MockOrderModelMockRecorder) ListOrder(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOrder", reflect.TypeOf((*MockOrderModel)(nil).ListOrder), ctx, req) +} + +// Update mocks base method. +func (m *MockOrderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", ctx, data) + ret0, _ := ret[0].(*mongo0.UpdateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockOrderModelMockRecorder) Update(ctx, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrderModel)(nil).Update), ctx, data) +} + +// UpdateStatus mocks base method. +func (m *MockOrderModel) UpdateStatus(ctx context.Context, data mongo.UpdateStatusReq) (*mongo0.UpdateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateStatus", ctx, data) + ret0, _ := ret[0].(*mongo0.UpdateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateStatus indicates an expected call of UpdateStatus. +func (mr *MockOrderModelMockRecorder) UpdateStatus(ctx, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockOrderModel)(nil).UpdateStatus), ctx, data) +} + +// UpdateTimeoutOrder mocks base method. +func (m *MockOrderModel) UpdateTimeoutOrder(ctx context.Context, req mongo.UpdateTimeoutReq) (*mongo0.UpdateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTimeoutOrder", ctx, req) + ret0, _ := ret[0].(*mongo0.UpdateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateTimeoutOrder indicates an expected call of UpdateTimeoutOrder. +func (mr *MockOrderModelMockRecorder) UpdateTimeoutOrder(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTimeoutOrder", reflect.TypeOf((*MockOrderModel)(nil).UpdateTimeoutOrder), ctx, req) +} diff --git a/internal/mock/model/order_model_gen.go b/internal/mock/model/order_model_gen.go new file mode 100644 index 0000000..30e4f36 --- /dev/null +++ b/internal/mock/model/order_model_gen.go @@ -0,0 +1,101 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/model/mongo/order_model_gen.go +// +// Generated by this command: +// +// mockgen -source=./internal/model/mongo/order_model_gen.go -destination=./internal/mock/model/order_model_gen.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + mongo "app-cloudep-trade-service/internal/model/mongo" + context "context" + reflect "reflect" + + mongo0 "go.mongodb.org/mongo-driver/mongo" + gomock "go.uber.org/mock/gomock" +) + +// MockorderModel is a mock of orderModel interface. +type MockorderModel struct { + ctrl *gomock.Controller + recorder *MockorderModelMockRecorder +} + +// MockorderModelMockRecorder is the mock recorder for MockorderModel. +type MockorderModelMockRecorder struct { + mock *MockorderModel +} + +// NewMockorderModel creates a new mock instance. +func NewMockorderModel(ctrl *gomock.Controller) *MockorderModel { + mock := &MockorderModel{ctrl: ctrl} + mock.recorder = &MockorderModelMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockorderModel) EXPECT() *MockorderModelMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockorderModel) Delete(ctx context.Context, id string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, id) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delete indicates an expected call of Delete. +func (mr *MockorderModelMockRecorder) Delete(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockorderModel)(nil).Delete), ctx, id) +} + +// FindOne mocks base method. +func (m *MockorderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindOne", ctx, id) + ret0, _ := ret[0].(*mongo.Order) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindOne indicates an expected call of FindOne. +func (mr *MockorderModelMockRecorder) FindOne(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockorderModel)(nil).FindOne), ctx, id) +} + +// Insert mocks base method. +func (m *MockorderModel) Insert(ctx context.Context, data *mongo.Order) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Insert", ctx, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert. +func (mr *MockorderModelMockRecorder) Insert(ctx, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockorderModel)(nil).Insert), ctx, data) +} + +// Update mocks base method. +func (m *MockorderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", ctx, data) + ret0, _ := ret[0].(*mongo0.UpdateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockorderModelMockRecorder) Update(ctx, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockorderModel)(nil).Update), ctx, data) +} diff --git a/internal/model/mongo/error.go b/internal/model/mongo/error.go new file mode 100644 index 0000000..27d9244 --- /dev/null +++ b/internal/model/mongo/error.go @@ -0,0 +1,12 @@ +package model + +import ( + "errors" + + "github.com/zeromicro/go-zero/core/stores/mon" +) + +var ( + ErrNotFound = mon.ErrNotFound + ErrInvalidObjectId = errors.New("invalid objectId") +) diff --git a/internal/model/mongo/order_model.go b/internal/model/mongo/order_model.go new file mode 100644 index 0000000..2da7c31 --- /dev/null +++ b/internal/model/mongo/order_model.go @@ -0,0 +1,238 @@ +package model + +import ( + "app-cloudep-trade-service/internal/domain" + "context" + "errors" + "time" + + "github.com/zeromicro/go-zero/core/stores/mon" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var _ OrderModel = (*customOrderModel)(nil) + +type ( + // OrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customOrderModel. + OrderModel interface { + orderModel + UpdateStatus(ctx context.Context, data UpdateStatusReq) (*mongo.UpdateResult, error) + UpdateTimeoutOrder(ctx context.Context, req UpdateTimeoutReq) (*mongo.UpdateResult, error) + DeleteByBusinessID(ctx context.Context, id string) (*mongo.UpdateResult, error) + FindOneBusinessID(ctx context.Context, id string) (*Order, error) + ListOrder(ctx context.Context, req GetOrderListReq) ([]Order, int64, error) + } + + customOrderModel struct { + *defaultOrderModel + } + UpdateStatusReq struct { + BusinessID string + Status int64 + } + + UpdateTimeoutReq struct { + CreateTimeBefore int64 + } + + GetOrderListReq struct { + PageIndex int64 `json:"page_index" validate:"required"` + PageSize int64 `json:"page_size" validate:"required"` + + ReferenceID string `json:"reference_id"` + ReferenceUID string `json:"reference_uid"` + BusinessID string `json:"business_id"` + UID string `json:"uid"` + OrderType int `json:"order_type"` + DirectionType []int64 `json:"direction_type"` + OrderStatus []int64 `json:"order_status"` + + StartCreateTime int64 `json:"start_create_time"` + EndCreateTime int64 `json:"end_create_time"` + StartUpdateTime int64 `json:"start_update_time"` + EndUpdateTime int64 `json:"end_update_time"` + StartOrderArrivalTime int64 `json:"start_order_arrival_time"` + EndOrderArrivalTime int64 `json:"end_order_arrival_time"` + StartOrderPaymentTime int64 `json:"start_order_payment_time"` + EndOrderPaymentTime int64 `json:"end_order_payment_time"` + + CryptoType string `json:"crypto_type"` + TxHash string `json:"tx_hash"` + } +) + +// NewOrderModel returns a model for the mongo. +func NewOrderModel(url, db, collection string, opts ...mon.Option) OrderModel { + conn := mon.MustNewModel(url, db, collection, opts...) + return &customOrderModel{ + defaultOrderModel: newDefaultOrderModel(conn), + } +} + +func (m *customOrderModel) UpdateStatus(ctx context.Context, data UpdateStatusReq) (*mongo.UpdateResult, error) { + // 初始化 updates map + updates := make(map[string]any) + + // 更新 update_time 和 status + updates["update_time"] = time.Now().UTC().UnixNano() + updates["order_status"] = data.Status + + // 使用 updates map 作為 $set 的內容 + res, err := m.conn.UpdateOne(ctx, + bson.M{ + "business_id": data.BusinessID, + "$or": []bson.M{ + {"delete_time": bson.M{"$exists": false}}, + {"delete_time": 0}, + }, + }, + bson.M{"$set": updates}, // 使用 updates 而不是整個 data + ) + + return res, err +} + +func (m *customOrderModel) UpdateTimeoutOrder(ctx context.Context, req UpdateTimeoutReq) (*mongo.UpdateResult, error) { + // 構建過濾條件,選擇創建時間在指定時間之前且狀態為 0 (創建) 的項目 + filter := bson.M{ + "create_time": bson.M{"$lt": req.CreateTimeBefore}, + "status": domain.OrderStatusCreated, + "$or": []bson.M{ + {"delete_time": bson.M{"$exists": false}}, + {"delete_time": 0}, + }, + } + + // 更新內容,將狀態設置為 11,並更新 update_time + updates := bson.M{ + "$set": bson.M{ + "status": domain.OrderStatusTimeout, + "update_time": time.Now().UTC().UnixNano(), + }, + } + + // 執行更新操作 + res, err := m.conn.UpdateOne(ctx, filter, updates) + return res, err +} + +func (m *customOrderModel) DeleteByBusinessID(ctx context.Context, id string) (*mongo.UpdateResult, error) { + filter := bson.M{ + "business_id": id, + "$or": []bson.M{ + {"delete_time": bson.M{"$exists": false}}, + {"delete_time": 0}, + }, + } + + updates := bson.M{ + "$set": bson.M{ + "delete_time": time.Now().UTC().UnixNano(), + "update_time": time.Now().UTC().UnixNano(), + }, + } + // 執行更新操作 + res, err := m.conn.UpdateOne(ctx, filter, updates) + + return res, err +} + +func (m *defaultOrderModel) FindOneBusinessID(ctx context.Context, id string) (*Order, error) { + var data Order + filter := bson.M{"delete_time": bson.M{"$in": []any{0, nil}}} + filter["business_id"] = id + + err := m.conn.FindOne(ctx, &data, filter) + switch { + case err == nil: + return &data, nil + case errors.Is(err, mon.ErrNotFound): + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *customOrderModel) ListOrder(ctx context.Context, req GetOrderListReq) ([]Order, int64, error) { + // 定義查詢過濾器 + filter := bson.M{ + "delete_time": bson.M{"$in": []any{0, nil}}, + } + + // 添加查詢條件 + if req.ReferenceID != "" { + filter["reference_id"] = req.ReferenceID + } + if req.ReferenceUID != "" { + filter["reference_uid"] = req.ReferenceUID + } + if req.BusinessID != "" { + filter["business_id"] = req.BusinessID + } + if req.UID != "" { + filter["order_uid"] = req.UID + } + if req.OrderType != 0 { + filter["order_type"] = req.OrderType + } + if len(req.DirectionType) > 0 { + filter["direction_type"] = bson.M{"$in": req.DirectionType} + } + if len(req.OrderStatus) > 0 { + filter["order_status"] = bson.M{"$in": req.OrderStatus} + } + // 處理時間範圍 + if req.StartCreateTime != 0 { + filter["create_time"] = bson.M{"$gte": req.StartCreateTime} + } + if req.EndCreateTime != 0 { + filter["create_time"] = bson.M{"$lte": req.EndCreateTime} + } + if req.StartUpdateTime != 0 { + filter["update_time"] = bson.M{"$gte": req.StartUpdateTime} + } + if req.EndUpdateTime != 0 { + filter["update_time"] = bson.M{"$lte": req.EndUpdateTime} + } + if req.StartOrderArrivalTime != 0 { + filter["order_arrival_time"] = bson.M{"$gte": req.StartOrderArrivalTime} + } + if req.EndOrderArrivalTime != 0 { + filter["order_arrival_time"] = bson.M{"$lte": req.EndOrderArrivalTime} + } + if req.StartOrderPaymentTime != 0 { + filter["order_payment_time"] = bson.M{"$gte": req.StartOrderPaymentTime} + } + if req.EndOrderPaymentTime != 0 { + filter["order_payment_time"] = bson.M{"$lte": req.EndOrderPaymentTime} + } + if req.CryptoType != "" { + filter["crypto_type"] = req.CryptoType + } + if req.TxHash != "" { + filter["tx_hash"] = req.TxHash + } + + // 計算符合條件的總數量 + totalCount, err := m.conn.CountDocuments(ctx, filter) + if err != nil { + return nil, 0, err + } + + // 設定查詢選項,包含分頁 + findOptions := options.Find() + findOptions.SetSkip((req.PageIndex - 1) * req.PageSize) + findOptions.SetLimit(req.PageSize) + + // 執行查詢 + var orders []Order + err = m.conn.Find(ctx, &orders, filter, findOptions) + if err != nil { + return nil, 0, err + } + + return orders, totalCount, nil +} diff --git a/internal/model/mongo/order_model_gen.go b/internal/model/mongo/order_model_gen.go new file mode 100644 index 0000000..9a082a6 --- /dev/null +++ b/internal/model/mongo/order_model_gen.go @@ -0,0 +1,75 @@ +// Code generated by goctl. DO NOT EDIT. +package model + +import ( + "context" + "time" + + "github.com/zeromicro/go-zero/core/stores/mon" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type orderModel interface { + Insert(ctx context.Context, data *Order) error + FindOne(ctx context.Context, id string) (*Order, error) + Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) + Delete(ctx context.Context, id string) (int64, error) +} + +type defaultOrderModel struct { + conn *mon.Model +} + +func newDefaultOrderModel(conn *mon.Model) *defaultOrderModel { + return &defaultOrderModel{conn: conn} +} + +func (m *defaultOrderModel) Insert(ctx context.Context, data *Order) error { + if data.ID.IsZero() { + now := time.Now().UTC().UnixNano() + data.ID = primitive.NewObjectID() + data.CreateTime = now + data.UpdateTime = now + } + + _, err := m.conn.InsertOne(ctx, data) + return err +} + +func (m *defaultOrderModel) FindOne(ctx context.Context, id string) (*Order, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, ErrInvalidObjectId + } + + var data Order + + err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid}) + switch err { + case nil: + return &data, nil + case mon.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultOrderModel) Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) { + data.UpdateTime = time.Now().UTC().UnixNano() + + res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data}) + return res, err +} + +func (m *defaultOrderModel) Delete(ctx context.Context, id string) (int64, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return 0, ErrInvalidObjectId + } + + res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid}) + return res, err +} diff --git a/internal/model/mongo/order_types.go b/internal/model/mongo/order_types.go new file mode 100644 index 0000000..db23980 --- /dev/null +++ b/internal/model/mongo/order_types.go @@ -0,0 +1,53 @@ +package model + +import ( + "app-cloudep-trade-service/internal/domain" + + "github.com/shopspring/decimal" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Order struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + UpdateTime int64 `bson:"update_time"` + CreateTime int64 `bson:"create_time"` + BusinessID string `bson:"business_id"` // 訂單業務流水號 + OrderType domain.OrderType `bson:"order_type"` // 訂單類型 + OrderStatus domain.OrderStatus `bson:"order_status"` // 訂單狀態 + Brand string `bson:"brand"` // 下單平台 + OrderUID string `bson:"order_uid"` // 下單用戶 UID + ReferenceID string `bson:"reference_id"` // 訂單來源 + Count decimal.Decimal `bson:"count"` // 訂單數量 + OrderFee decimal.Decimal `bson:"order_fee"` // 訂單手續費 + Amount decimal.Decimal `bson:"amount"` // 單價 + WalletStatus int64 `bson:"wallet_status,omitempty"` // 交易金額狀態 + DirectionType int64 `bson:"direction_type,omitempty"` // 交易方向 + // 以上為必要欄位,下面是區塊鏈時才需要 + ReferenceBrand *string `bson:"reference_brand,omitempty"` // 訂單來源平台 + ReferenceUID *string `bson:"reference_uid,omitempty"` // 訂單來源用戶 UID + ThreePartyStatus *int64 `bson:"three_party_status,omitempty"` // 三方請求狀態 + CryptoType *string `bson:"crypto_type,omitempty"` // 交易幣種 + ThirdPartyFee *decimal.Decimal `bson:"third_party_fee,omitempty"` // 第三方手續費 + CryptoToUSDTRate *decimal.Decimal `bson:"crypto_to_usdt_rate,omitempty"` // 加密貨幣對 USDT 匯率 + FiatToUSDRate *decimal.Decimal `bson:"fiat_to_usd_rate,omitempty"` // 法幣對 USD 匯率 + FeeCryptoToUSDTRate *decimal.Decimal `bson:"fee_crypto_to_usdt_rate,omitempty"` // 手續費加密貨幣對 USDT 匯率 + USDTToCryptoTypeRate *decimal.Decimal `bson:"usdt_to_crypto_type_rate,omitempty"` // USDT 對加密貨幣匯率 + PaymentFiat *string `bson:"payment_fiat,omitempty"` // 支付法幣 + PaymentUnitPrice *decimal.Decimal `bson:"payment_unit_price,omitempty"` // 加密貨幣單價 + PaymentTemplateID *string `bson:"payment_template_id,omitempty"` // 支付方式配置 ID + OrderArrivalTime *int64 `bson:"order_arrival_time,omitempty"` // 訂單到帳時間 + OrderPaymentTime *int64 `bson:"order_payment_time,omitempty"` // 訂單付款時間 + UnpaidTimeoutSecond *int64 `bson:"unpaid_timeout_second,omitempty"` // 支付期限秒數 + ChainType *string `bson:"chain_type,omitempty"` // 主網類型 + TxHash *string `bson:"tx_hash,omitempty"` // 交易哈希 + FromAddress *string `bson:"from_address,omitempty"` // 來源地址 + ToAddress *string `bson:"to_address,omitempty"` // 目標地址 + ChainFee *decimal.Decimal `bson:"chain_fee,omitempty"` // 鏈上交易手續費 + ChainFeeCrypto *string `bson:"chain_fee_crypto,omitempty"` // 鏈上手續費使用幣別 + Memo *string `bson:"memo,omitempty"` // 鏈上備註 + OrderNote *string `bson:"order_note,omitempty"` // 訂單交易備註 +} + +func (p *Order) CollectionName() string { + return "order" +} diff --git a/internal/server/couponservice/coupon_service_server.go b/internal/server/couponservice/coupon_service_server.go new file mode 100644 index 0000000..948ec75 --- /dev/null +++ b/internal/server/couponservice/coupon_service_server.go @@ -0,0 +1,59 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + couponservicelogic "app-cloudep-trade-service/internal/logic/couponservice" + "app-cloudep-trade-service/internal/svc" +) + +type CouponServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedCouponServiceServer +} + +func NewCouponServiceServer(svcCtx *svc.ServiceContext) *CouponServiceServer { + return &CouponServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateCoupon 創建優惠券 +func (s *CouponServiceServer) CreateCoupon(ctx context.Context, in *trade.CreateCouponReq) (*trade.CouponResp, error) { + l := couponservicelogic.NewCreateCouponLogic(ctx, s.svcCtx) + return l.CreateCoupon(in) +} + +// UpdateCoupon更新優惠券 +func (s *CouponServiceServer) UpdateCoupon(ctx context.Context, in *trade.UpdateCouponReq) (*trade.CouponResp, error) { + l := couponservicelogic.NewUpdateCouponLogic(ctx, s.svcCtx) + return l.UpdateCoupon(in) +} + +// QueryCoupon查詢優惠券 +func (s *CouponServiceServer) QueryCoupon(ctx context.Context, in *trade.CouponQueryReq) (*trade.CouponResp, error) { + l := couponservicelogic.NewQueryCouponLogic(ctx, s.svcCtx) + return l.QueryCoupon(in) +} + +// DeleteCoupon 刪除優惠券 +func (s *CouponServiceServer) DeleteCoupon(ctx context.Context, in *trade.CouponQueryReq) (*trade.OKResp, error) { + l := couponservicelogic.NewDeleteCouponLogic(ctx, s.svcCtx) + return l.DeleteCoupon(in) +} + +// ListCoupons列出所有優惠券 +func (s *CouponServiceServer) ListCoupons(ctx context.Context, in *trade.CouponListReq) (*trade.CouponListResp, error) { + l := couponservicelogic.NewListCouponsLogic(ctx, s.svcCtx) + return l.ListCoupons(in) +} + +// ValidateCoupon 驗證優惠券 (在下單或支付時使用) +func (s *CouponServiceServer) ValidateCoupon(ctx context.Context, in *trade.ValidateCouponReq) (*trade.CouponValidationResp, error) { + l := couponservicelogic.NewValidateCouponLogic(ctx, s.svcCtx) + return l.ValidateCoupon(in) +} diff --git a/internal/server/inventoryservice/inventory_service_server.go b/internal/server/inventoryservice/inventory_service_server.go new file mode 100644 index 0000000..beb685f --- /dev/null +++ b/internal/server/inventoryservice/inventory_service_server.go @@ -0,0 +1,71 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + inventoryservicelogic "app-cloudep-trade-service/internal/logic/inventoryservice" + "app-cloudep-trade-service/internal/svc" +) + +type InventoryServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedInventoryServiceServer +} + +func NewInventoryServiceServer(svcCtx *svc.ServiceContext) *InventoryServiceServer { + return &InventoryServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateStock 建立庫存品項 +func (s *InventoryServiceServer) CreateStock(ctx context.Context, in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewCreateStockLogic(ctx, s.svcCtx) + return l.CreateStock(in) +} + +// AddStock 增加庫存 +func (s *InventoryServiceServer) AddStock(ctx context.Context, in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewAddStockLogic(ctx, s.svcCtx) + return l.AddStock(in) +} + +// ReduceStock 減少庫存 +func (s *InventoryServiceServer) ReduceStock(ctx context.Context, in *trade.StockAdjustmentReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewReduceStockLogic(ctx, s.svcCtx) + return l.ReduceStock(in) +} + +// QueryStock 查詢單一商品的庫存 +func (s *InventoryServiceServer) QueryStock(ctx context.Context, in *trade.StockQueryReq) (*trade.StockResp, error) { + l := inventoryservicelogic.NewQueryStockLogic(ctx, s.svcCtx) + return l.QueryStock(in) +} + +// ListAllStock 查詢所有商品的庫存狀況 +func (s *InventoryServiceServer) ListAllStock(ctx context.Context, in *trade.StockListReq) (*trade.StockListResp, error) { + l := inventoryservicelogic.NewListAllStockLogic(ctx, s.svcCtx) + return l.ListAllStock(in) +} + +// ReserveStock 預留庫存 (用於未完成的訂單) +func (s *InventoryServiceServer) ReserveStock(ctx context.Context, in *trade.StockReservationReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewReserveStockLogic(ctx, s.svcCtx) + return l.ReserveStock(in) +} + +// ReleaseReservedStock 釋放預留庫存 (取消或失效的訂單) +func (s *InventoryServiceServer) ReleaseReservedStock(ctx context.Context, in *trade.StockReservationReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewReleaseReservedStockLogic(ctx, s.svcCtx) + return l.ReleaseReservedStock(in) +} + +// AdjustStock 調整庫存 (批量修改庫存,用於大批商品更新) +func (s *InventoryServiceServer) AdjustStock(ctx context.Context, in *trade.BatchStockAdjustmentReq) (*trade.OKResp, error) { + l := inventoryservicelogic.NewAdjustStockLogic(ctx, s.svcCtx) + return l.AdjustStock(in) +} diff --git a/internal/server/orderservice/order_service_server.go b/internal/server/orderservice/order_service_server.go new file mode 100644 index 0000000..94d7ec1 --- /dev/null +++ b/internal/server/orderservice/order_service_server.go @@ -0,0 +1,65 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + orderservicelogic "app-cloudep-trade-service/internal/logic/orderservice" + "app-cloudep-trade-service/internal/svc" +) + +type OrderServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedOrderServiceServer +} + +func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer { + return &OrderServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateOrder 建立訂單 +func (s *OrderServiceServer) CreateOrder(ctx context.Context, in *trade.CreateOrderReq) (*trade.OKResp, error) { + l := orderservicelogic.NewCreateOrderLogic(ctx, s.svcCtx) + return l.CreateOrder(in) +} + +// CancelOrder 取消訂單 +func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *trade.CancelOrderReq) (*trade.OKResp, error) { + l := orderservicelogic.NewCancelOrderLogic(ctx, s.svcCtx) + return l.CancelOrder(in) +} + +// ModifyOrderStatus 修改訂單狀態 +func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *trade.ModifyOrderStatusReq) (*trade.OKResp, error) { + l := orderservicelogic.NewModifyOrderStatusLogic(ctx, s.svcCtx) + return l.ModifyOrderStatus(in) +} + +// DeleteOrder 刪除訂單(軟刪除) +func (s *OrderServiceServer) DeleteOrder(ctx context.Context, in *trade.DeleteOrderReq) (*trade.OKResp, error) { + l := orderservicelogic.NewDeleteOrderLogic(ctx, s.svcCtx) + return l.DeleteOrder(in) +} + +// GetOrder 取得訂單詳情 +func (s *OrderServiceServer) GetOrder(ctx context.Context, in *trade.GetOrderReq) (*trade.GetOrderResp, error) { + l := orderservicelogic.NewGetOrderLogic(ctx, s.svcCtx) + return l.GetOrder(in) +} + +// ListOrder 取得訂單列表 +func (s *OrderServiceServer) ListOrder(ctx context.Context, in *trade.ListOrderReq) (*trade.ListOrderResp, error) { + l := orderservicelogic.NewListOrderLogic(ctx, s.svcCtx) + return l.ListOrder(in) +} + +// OrderStatusTimeout 訂單超時任務/cron/order-status/timeout +func (s *OrderServiceServer) OrderStatusTimeout(ctx context.Context, in *trade.OrderStatusTimeoutReq) (*trade.OKResp, error) { + l := orderservicelogic.NewOrderStatusTimeoutLogic(ctx, s.svcCtx) + return l.OrderStatusTimeout(in) +} diff --git a/internal/server/productservice/product_service_server.go b/internal/server/productservice/product_service_server.go new file mode 100644 index 0000000..c0b552e --- /dev/null +++ b/internal/server/productservice/product_service_server.go @@ -0,0 +1,53 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + productservicelogic "app-cloudep-trade-service/internal/logic/productservice" + "app-cloudep-trade-service/internal/svc" +) + +type ProductServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedProductServiceServer +} + +func NewProductServiceServer(svcCtx *svc.ServiceContext) *ProductServiceServer { + return &ProductServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateProduct 新增商品 +func (s *ProductServiceServer) CreateProduct(ctx context.Context, in *trade.ProductCreateReq) (*trade.ProductResp, error) { + l := productservicelogic.NewCreateProductLogic(ctx, s.svcCtx) + return l.CreateProduct(in) +} + +// UpdateProduct 更新商品資訊 +func (s *ProductServiceServer) UpdateProduct(ctx context.Context, in *trade.ProductUpdateReq) (*trade.ProductResp, error) { + l := productservicelogic.NewUpdateProductLogic(ctx, s.svcCtx) + return l.UpdateProduct(in) +} + +// DeleteProduct 刪除商品 +func (s *ProductServiceServer) DeleteProduct(ctx context.Context, in *trade.ProductDeleteReq) (*trade.OKResp, error) { + l := productservicelogic.NewDeleteProductLogic(ctx, s.svcCtx) + return l.DeleteProduct(in) +} + +// QueryProduct 查詢單一商品資訊 +func (s *ProductServiceServer) QueryProduct(ctx context.Context, in *trade.ProductQueryReq) (*trade.ProductResp, error) { + l := productservicelogic.NewQueryProductLogic(ctx, s.svcCtx) + return l.QueryProduct(in) +} + +// ListAllProducts 查詢所有商品資訊 +func (s *ProductServiceServer) ListAllProducts(ctx context.Context, in *trade.ProductListReq) (*trade.ProductListResp, error) { + l := productservicelogic.NewListAllProductsLogic(ctx, s.svcCtx) + return l.ListAllProducts(in) +} diff --git a/internal/server/subscriptionservice/subscription_service_server.go b/internal/server/subscriptionservice/subscription_service_server.go new file mode 100644 index 0000000..c864be2 --- /dev/null +++ b/internal/server/subscriptionservice/subscription_service_server.go @@ -0,0 +1,71 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + subscriptionservicelogic "app-cloudep-trade-service/internal/logic/subscriptionservice" + "app-cloudep-trade-service/internal/svc" +) + +type SubscriptionServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedSubscriptionServiceServer +} + +func NewSubscriptionServiceServer(svcCtx *svc.ServiceContext) *SubscriptionServiceServer { + return &SubscriptionServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateSubscription 建立訂閱 +func (s *SubscriptionServiceServer) CreateSubscription(ctx context.Context, in *trade.SubscriptionCreateReq) (*trade.SubscriptionResp, error) { + l := subscriptionservicelogic.NewCreateSubscriptionLogic(ctx, s.svcCtx) + return l.CreateSubscription(in) +} + +// UpdateSubscription 更新訂閱設定 +func (s *SubscriptionServiceServer) UpdateSubscription(ctx context.Context, in *trade.SubscriptionUpdateReq) (*trade.SubscriptionResp, error) { + l := subscriptionservicelogic.NewUpdateSubscriptionLogic(ctx, s.svcCtx) + return l.UpdateSubscription(in) +} + +// CancelSubscription 取消訂閱 +func (s *SubscriptionServiceServer) CancelSubscription(ctx context.Context, in *trade.SubscriptionCancelReq) (*trade.OKResp, error) { + l := subscriptionservicelogic.NewCancelSubscriptionLogic(ctx, s.svcCtx) + return l.CancelSubscription(in) +} + +// QuerySubscription 查詢單一訂閱資訊 +func (s *SubscriptionServiceServer) QuerySubscription(ctx context.Context, in *trade.SubscriptionQueryReq) (*trade.SubscriptionResp, error) { + l := subscriptionservicelogic.NewQuerySubscriptionLogic(ctx, s.svcCtx) + return l.QuerySubscription(in) +} + +// ListSubscriptions 查詢所有訂閱 (用於管理和監控) +func (s *SubscriptionServiceServer) ListSubscriptions(ctx context.Context, in *trade.ListSubscriptionsReq) (*trade.SubscriptionListResp, error) { + l := subscriptionservicelogic.NewListSubscriptionsLogic(ctx, s.svcCtx) + return l.ListSubscriptions(in) +} + +// RenewSubscription 續訂訂閱 +func (s *SubscriptionServiceServer) RenewSubscription(ctx context.Context, in *trade.SubscriptionRenewReq) (*trade.SubscriptionResp, error) { + l := subscriptionservicelogic.NewRenewSubscriptionLogic(ctx, s.svcCtx) + return l.RenewSubscription(in) +} + +// CheckSubscriptionStatus 查詢訂閱狀態 (啟用/過期/取消) +func (s *SubscriptionServiceServer) CheckSubscriptionStatus(ctx context.Context, in *trade.SubscriptionStatusQueryReq) (*trade.SubscriptionStatusResp, error) { + l := subscriptionservicelogic.NewCheckSubscriptionStatusLogic(ctx, s.svcCtx) + return l.CheckSubscriptionStatus(in) +} + +// RefreshSubscriptionStatus cron 改變訂閱的狀態(時間到了要過期,需要續約自動續約),每 5 分鐘執行一次 +func (s *SubscriptionServiceServer) RefreshSubscriptionStatus(ctx context.Context, in *trade.NoneReq) (*trade.OKResp, error) { + l := subscriptionservicelogic.NewRefreshSubscriptionStatusLogic(ctx, s.svcCtx) + return l.RefreshSubscriptionStatus(in) +} diff --git a/internal/server/walletservice/wallet_service_server.go b/internal/server/walletservice/wallet_service_server.go new file mode 100644 index 0000000..4f0f60a --- /dev/null +++ b/internal/server/walletservice/wallet_service_server.go @@ -0,0 +1,89 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: trade.proto + +package server + +import ( + "context" + + "app-cloudep-trade-service/gen_result/pb/trade" + walletservicelogic "app-cloudep-trade-service/internal/logic/walletservice" + "app-cloudep-trade-service/internal/svc" +) + +type WalletServiceServer struct { + svcCtx *svc.ServiceContext + trade.UnimplementedWalletServiceServer +} + +func NewWalletServiceServer(svcCtx *svc.ServiceContext) *WalletServiceServer { + return &WalletServiceServer{ + svcCtx: svcCtx, + } +} + +// CreateWallet 建立錢包 +func (s *WalletServiceServer) CreateWallet(ctx context.Context, in *trade.CreateWalletReq) (*trade.OKResp, error) { + l := walletservicelogic.NewCreateWalletLogic(ctx, s.svcCtx) + return l.CreateWallet(in) +} + +// DepositFunds 充值 - 增加 [可用餘額] 或 [限制餘額] +func (s *WalletServiceServer) DepositFunds(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewDepositFundsLogic(ctx, s.svcCtx) + return l.DepositFunds(in) +} + +// WithdrawFunds 提現 - 減少 [可用餘額] +func (s *WalletServiceServer) WithdrawFunds(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewWithdrawFundsLogic(ctx, s.svcCtx) + return l.WithdrawFunds(in) +} + +// FreezeFunds 凍結 - 將 [可用餘額] 減少,加到 [凍結餘額] +func (s *WalletServiceServer) FreezeFunds(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewFreezeFundsLogic(ctx, s.svcCtx) + return l.FreezeFunds(in) +} + +// UnfreezeFunds 解凍 - 減少 [凍結餘額] +func (s *WalletServiceServer) UnfreezeFunds(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewUnfreezeFundsLogic(ctx, s.svcCtx) + return l.UnfreezeFunds(in) +} + +// AppendToFreeze 追加凍結 - 減少可用餘額,追加到現有的 [凍結餘額] (基於原order_id) +func (s *WalletServiceServer) AppendToFreeze(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewAppendToFreezeLogic(ctx, s.svcCtx) + return l.AppendToFreeze(in) +} + +// RollbackFreeze 凍結回滾 - 減少 [凍結餘額],加回 [可用餘額],不可指定金額,整筆訂單的凍結金額加回 [可用餘額] +func (s *WalletServiceServer) RollbackFreeze(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewRollbackFreezeLogic(ctx, s.svcCtx) + return l.RollbackFreeze(in) +} + +// CancelFreeze 取消凍結 - 減少 [凍結餘額],加回 [可用餘額],可指定金額 +func (s *WalletServiceServer) CancelFreeze(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewCancelFreezeLogic(ctx, s.svcCtx) + return l.CancelFreeze(in) +} + +// TransferToRestrictedFunds 限制 - 減少 [凍結餘額],轉移到另一個使用者的 [限制餘額] +func (s *WalletServiceServer) TransferToRestrictedFunds(ctx context.Context, in *trade.WalletTransactionReq) (*trade.OKResp, error) { + l := walletservicelogic.NewTransferToRestrictedFundsLogic(ctx, s.svcCtx) + return l.TransferToRestrictedFunds(in) +} + +// SettleRestrictedFunds 結算限制餘額 - 限制餘額在 T+N 天後自動失效,每日只能執行一次 +func (s *WalletServiceServer) SettleRestrictedFunds(ctx context.Context, in *trade.NoneReq) (*trade.OKResp, error) { + l := walletservicelogic.NewSettleRestrictedFundsLogic(ctx, s.svcCtx) + return l.SettleRestrictedFunds(in) +} + +// QueryBalance 餘額查詢 -> 依照日期查詢 +func (s *WalletServiceServer) QueryBalance(ctx context.Context, in *trade.QueryBalanceByDateReq) (*trade.QueryBalanceByDateResp, error) { + l := walletservicelogic.NewQueryBalanceLogic(ctx, s.svcCtx) + return l.QueryBalance(in) +} diff --git a/internal/svc/inject_mongo.go b/internal/svc/inject_mongo.go new file mode 100644 index 0000000..addbcc3 --- /dev/null +++ b/internal/svc/inject_mongo.go @@ -0,0 +1,20 @@ +package svc + +import ( + "app-cloudep-trade-service/internal/config" + mgo "app-cloudep-trade-service/internal/lib/mongo" + model "app-cloudep-trade-service/internal/model/mongo" +) + +// MustOrderModel 連線 order mongo 時 +func MustOrderModel(c config.Config) model.OrderModel { + orderCollection := model.Order{} + + return model.NewOrderModel( + mgo.MustMongoConnectURL(c), + c.Mongo.Database, + orderCollection.CollectionName(), + mgo.SetCustomDecimalType(), + mgo.InitMongoOptions(c), + ) +} diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go new file mode 100644 index 0000000..96a4274 --- /dev/null +++ b/internal/svc/service_context.go @@ -0,0 +1,38 @@ +package svc + +import ( + "app-cloudep-trade-service/internal/config" + duc "app-cloudep-trade-service/internal/domain/usecase" + "app-cloudep-trade-service/internal/usecase" + + ers "code.30cm.net/digimon/library-go/errs" + "code.30cm.net/digimon/library-go/errs/code" + vi "code.30cm.net/digimon/library-go/validator" +) + +type ServiceContext struct { + Config config.Config + Validate vi.Validate + + OrderUseCase duc.OrderUseCase +} + +func NewServiceContext(c config.Config) *ServiceContext { + // TODO 改成 Trade + ers.Scope = code.CloudEPOrder + + om := MustOrderModel(c) + orderUseCase := usecase.NewOrderUseCase(usecase.OrderUseCaseParam{ + OrderModel: om, + }) + + return &ServiceContext{ + Config: c, + Validate: vi.MustValidator( + WithDecimalGt(), + WithDecimalGte(), + ), + + OrderUseCase: orderUseCase, + } +} diff --git a/internal/svc/validator.go b/internal/svc/validator.go new file mode 100644 index 0000000..f5104d5 --- /dev/null +++ b/internal/svc/validator.go @@ -0,0 +1,55 @@ +package svc + +import ( + vi "code.30cm.net/digimon/library-go/validator" + "github.com/go-playground/validator/v10" + "github.com/shopspring/decimal" +) + +// WithDecimalGt 是否大於等於 +func WithDecimalGt() vi.Option { + return vi.Option{ + ValidatorName: "decimalGt", + ValidatorFunc: func(fl validator.FieldLevel) bool { + if val, ok := fl.Field().Interface().(string); ok { + value, err := decimal.NewFromString(val) + if err != nil { + return false + } + + conditionValue, err := decimal.NewFromString(fl.Param()) + if err != nil { + return false + } + + return value.GreaterThan(conditionValue) + } + + return true + }, + } +} + +// WithDecimalGte 是否大於等於 +func WithDecimalGte() vi.Option { + return vi.Option{ + ValidatorName: "decimalGte", + ValidatorFunc: func(fl validator.FieldLevel) bool { + if val, ok := fl.Field().Interface().(string); ok { + value, err := decimal.NewFromString(val) + if err != nil { + return false + } + + conditionValue, err := decimal.NewFromString(fl.Param()) + if err != nil { + return false + } + + return value.GreaterThanOrEqual(conditionValue) + } + + return true + }, + } +} diff --git a/internal/svc/validator_test.go b/internal/svc/validator_test.go new file mode 100644 index 0000000..6b14b07 --- /dev/null +++ b/internal/svc/validator_test.go @@ -0,0 +1,106 @@ +package svc + +import ( + "testing" + + vi "code.30cm.net/digimon/library-go/validator" +) + +func TestWithDecimalGte(t *testing.T) { + validate := vi.MustValidator(WithDecimalGt(), WithDecimalGte()) + + type TestStruct struct { + Value string `validate:"decimalGte=10.50"` + } + + tests := []struct { + name string + input TestStruct + wantErr bool + }{ + { + name: "valid - equal", + input: TestStruct{Value: "10.50"}, + wantErr: false, + }, + { + name: "valid - greater", + input: TestStruct{Value: "15.00"}, + wantErr: false, + }, + { + name: "invalid - less", + input: TestStruct{Value: "9.99"}, + wantErr: true, + }, + { + name: "invalid - not a decimal", + input: TestStruct{Value: "abc"}, + wantErr: true, + }, + { + name: "valid - empty string", + input: TestStruct{Value: ""}, + wantErr: true, // Assuming empty string is valid + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validate.ValidateAll(tt.input) + + if (err != nil) != tt.wantErr { + t.Errorf("TestWithDecimalGte() %s = error %v, wantErr %v", tt.name, err, tt.wantErr) + } + }) + } +} + +func TestWithDecimalGt(t *testing.T) { + validate := vi.MustValidator(WithDecimalGt(), WithDecimalGt()) + + type TestStruct struct { + Value string `validate:"decimalGt=10.50"` + } + + tests := []struct { + name string + input TestStruct + wantErr bool + }{ + { + name: "valid - greater", + input: TestStruct{Value: "10.51"}, + wantErr: false, + }, + { + name: "invalid - equal", + input: TestStruct{Value: "10.50"}, + wantErr: true, // should fail because the value is equal to the condition + }, + { + name: "invalid - less", + input: TestStruct{Value: "9.99"}, + wantErr: true, + }, + { + name: "invalid - not a decimal", + input: TestStruct{Value: "abc"}, + wantErr: true, + }, + { + name: "valid - empty string", + input: TestStruct{Value: ""}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validate.ValidateAll(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("TestWithDecimalGt() %s = error %v, wantErr %v", tt.name, err, tt.wantErr) + } + }) + } +} diff --git a/internal/usecase/order.go b/internal/usecase/order.go new file mode 100644 index 0000000..c4005bb --- /dev/null +++ b/internal/usecase/order.go @@ -0,0 +1,310 @@ +package usecase + +import ( + "app-cloudep-trade-service/internal/domain" + "app-cloudep-trade-service/internal/domain/usecase" + model "app-cloudep-trade-service/internal/model/mongo" + "context" + "errors" + "time" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/mon" +) + +type OrderUseCaseParam struct { + OrderModel model.OrderModel +} + +type OrderUseCase struct { + OrderModel model.OrderModel +} + +func NewOrderUseCase(param OrderUseCaseParam) usecase.OrderUseCase { + return &OrderUseCase{ + OrderModel: param.OrderModel, + } +} + +func (o *OrderUseCase) CreateOrder(ctx context.Context, param usecase.CreateOrderReq) error { + now := time.Now().UTC().UnixNano() + // 插入資料庫 + order := &model.Order{ + UpdateTime: now, + CreateTime: now, + BusinessID: param.BusinessID, + OrderType: param.OrderType, + OrderStatus: param.OrderStatus, + Brand: param.Brand, + OrderUID: param.OrderUID, + ReferenceID: param.ReferenceID, + Count: param.Count, + OrderFee: param.OrderFee, + Amount: param.Amount, + ReferenceBrand: param.ReferenceBrand, + ReferenceUID: param.ReferenceUID, + WalletStatus: param.WalletStatus, + ThreePartyStatus: param.ThreePartyStatus, + DirectionType: param.DirectionType, + CryptoType: param.CryptoType, + ThirdPartyFee: param.ThirdPartyFee, + CryptoToUSDTRate: param.CryptoToUSDTRate, + FiatToUSDRate: param.FiatToUSDRate, + FeeCryptoToUSDTRate: param.FeeCryptoToUSDTRate, + USDTToCryptoTypeRate: param.USDTToCryptoTypeRate, + PaymentFiat: param.PaymentFiat, + PaymentUnitPrice: param.PaymentUnitPrice, + PaymentTemplateID: param.PaymentTemplateID, + OrderArrivalTime: param.OrderArrivalTime, + OrderPaymentTime: param.OrderPaymentTime, + UnpaidTimeoutSecond: param.UnpaidTimeoutSecond, + ChainType: param.ChainType, + TxHash: param.TxHash, + FromAddress: param.FromAddress, + ToAddress: param.ToAddress, + ChainFee: param.ChainFee, + ChainFeeCrypto: param.ChainFeeCrypto, + Memo: param.Memo, + OrderNote: param.OrderNote, + } + + err := o.OrderModel.Insert(ctx, order) + + if err != nil { + // 錯誤代碼 06-021-01 + e := domain.CommentErrorL( + domain.CreateOrderErrorCode, + logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: param}, + {Key: "func", Value: "OrderModel.Insert"}, + {Key: "err", Value: err}, + }, + "failed to insert order into mongo:").Wrap(err) + + return e + } + + return nil +} + +func (o *OrderUseCase) CancelOrder(ctx context.Context, param usecase.CancelOrderQuery) error { + _, err := o.OrderModel.UpdateStatus(ctx, model.UpdateStatusReq{ + BusinessID: param.BusinessID, + Status: param.Status.ToInt64(), + }) + + if err != nil { + // 錯誤代碼 06-021-02 + e := domain.CommentErrorL( + domain.CancelOrderErrorCode, + logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: param}, + {Key: "func", Value: "OrderModel.UpdateStatus"}, + {Key: "err", Value: err}, + }, + "failed to update order status:").Wrap(err) + + return e + } + + return nil +} + +func (o *OrderUseCase) DeleteOrder(ctx context.Context, param usecase.DeleteOrderQuery) error { + _, err := o.OrderModel.DeleteByBusinessID(ctx, param.BusinessID) + if err != nil { + return err + } + + return nil +} + +func (o *OrderUseCase) GetOrder(ctx context.Context, param usecase.GetOrderQuery) (*usecase.GetOrderResp, error) { + order, err := o.OrderModel.FindOneBusinessID(ctx, param.BusinessID) + if err != nil { + if errors.Is(mon.ErrNotFound, err) { + return nil, domain.NotFoundError(domain.DataNotFoundErrorCode, "failed to get this order id:", param.BusinessID) + } + + return nil, err + } + + resp := &usecase.GetOrderResp{ + UpdateTime: order.UpdateTime, + CreateTime: order.CreateTime, + BusinessID: order.BusinessID, + OrderType: order.OrderType, + OrderStatus: order.OrderStatus, + Brand: order.Brand, + OrderUID: order.OrderUID, + ReferenceID: order.ReferenceID, + Count: order.Count.String(), + OrderFee: order.OrderFee.String(), + Amount: order.Amount.String(), + ReferenceBrand: order.ReferenceBrand, + ReferenceUID: order.ReferenceUID, + WalletStatus: Int64Ptr(order.WalletStatus), + ThreePartyStatus: order.ThreePartyStatus, + DirectionType: Int64Ptr(order.DirectionType), + CryptoType: order.CryptoType, + ThirdPartyFee: DecimalToStringPtr(order.ThirdPartyFee), + CryptoToUsdtRate: DecimalToStringPtr(order.CryptoToUSDTRate), + FiatToUsdRate: DecimalToStringPtr(order.FiatToUSDRate), + FeeCryptoToUsdtRate: DecimalToStringPtr(order.FeeCryptoToUSDTRate), + UsdtToCryptoTypeRate: DecimalToStringPtr(order.USDTToCryptoTypeRate), + PaymentFiat: order.PaymentFiat, + PaymentUnitPrice: DecimalToStringPtr(order.PaymentUnitPrice), + PaymentTemplateID: order.PaymentTemplateID, + OrderArrivalTime: order.OrderArrivalTime, + OrderPaymentTime: order.OrderPaymentTime, + UnpaidTimeoutSecond: order.UnpaidTimeoutSecond, + ChainType: order.ChainType, + TxHash: order.TxHash, + FromAddress: order.FromAddress, + ToAddress: order.ToAddress, + ChainFee: DecimalToStringPtr(order.ChainFee), + ChainFeeCrypto: order.ChainFeeCrypto, + Memo: order.Memo, + OrderNote: order.OrderNote, + } + + return resp, nil +} + +func (o *OrderUseCase) ListOrder(ctx context.Context, param usecase.GetOrderListReq) (*usecase.ListOrderResp, error) { + // 構建查詢條件 + req := model.GetOrderListReq{ + PageIndex: param.PageIndex, + PageSize: param.PageSize, + ReferenceID: param.ReferenceID, + ReferenceUID: param.ReferenceUID, + BusinessID: param.BusinessID, + UID: param.UID, + OrderType: param.OrderType.ToInt(), + DirectionType: param.DirectionType, + OrderStatus: param.OrderStatus, + StartCreateTime: param.StartCreateTime, + EndCreateTime: param.EndCreateTime, + StartUpdateTime: param.StartUpdateTime, + EndUpdateTime: param.EndUpdateTime, + StartOrderArrivalTime: param.StartOrderArrivalTime, + EndOrderArrivalTime: param.EndOrderArrivalTime, + StartOrderPaymentTime: param.StartOrderPaymentTime, + EndOrderPaymentTime: param.EndOrderPaymentTime, + CryptoType: param.CryptoType, + TxHash: param.TxHash, + } + + // 查詢訂單 + orders, total, err := o.OrderModel.ListOrder(ctx, req) + if err != nil { + return nil, err + } + + return &usecase.ListOrderResp{ + Data: o.convertOrdersToResponses(orders), + Page: &usecase.Pager{ + Total: total, + Index: param.PageIndex, + Size: param.PageSize, + }, + }, nil +} + +func (o *OrderUseCase) ModifyOrderStatus(ctx context.Context, param *usecase.ModifyOrderQuery) error { + _, err := o.OrderModel.UpdateStatus(ctx, + model.UpdateStatusReq{ + BusinessID: param.BusinessID, + Status: param.Status, + }) + if err != nil { + // 錯誤代碼 06-021-02 + e := domain.CommentErrorL( + domain.ModifyOrderErrorCode, + logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: param}, + {Key: "func", Value: "OrderModel.UpdateStatus"}, + {Key: "err", Value: err}, + }, + "failed to update order status:").Wrap(err) + + return e + } + + return nil +} + +func (o *OrderUseCase) OrderStatusTimeout(ctx context.Context) error { + now := time.Now().UTC().UnixNano() + _, err := o.OrderModel.UpdateTimeoutOrder(ctx, model.UpdateTimeoutReq{ + CreateTimeBefore: now, + }) + if err != nil { + // 錯誤代碼 06-021-02 + e := domain.CommentErrorL( + domain.TimeoutOrderErrorCode, + logx.WithContext(ctx), + []logx.LogField{ + {Key: "now", Value: now}, + {Key: "func", Value: "OrderModel.UpdateTimeoutOrder"}, + {Key: "err", Value: err}, + }, + "failed to update timeout order").Wrap(err) + + return e + } + + return nil +} + +// ================= 工具類區 ================= + +func (o *OrderUseCase) convertOrdersToResponses(orders []model.Order) []*usecase.GetOrderResp { + res := make([]*usecase.GetOrderResp, 0, len(orders)) + for _, order := range orders { + resp := &usecase.GetOrderResp{ + UpdateTime: order.UpdateTime, + CreateTime: order.CreateTime, + BusinessID: order.BusinessID, + OrderType: order.OrderType, + OrderStatus: order.OrderStatus, + Brand: order.Brand, + OrderUID: order.OrderUID, + ReferenceID: order.ReferenceID, + Count: order.Count.String(), + OrderFee: order.OrderFee.String(), + Amount: order.Amount.String(), + ReferenceBrand: order.ReferenceBrand, + ReferenceUID: order.ReferenceUID, + WalletStatus: Int64Ptr(order.WalletStatus), + ThreePartyStatus: order.ThreePartyStatus, + DirectionType: Int64Ptr(order.DirectionType), + CryptoType: order.CryptoType, + ThirdPartyFee: DecimalToStringPtr(order.ThirdPartyFee), + CryptoToUsdtRate: DecimalToStringPtr(order.CryptoToUSDTRate), + FiatToUsdRate: DecimalToStringPtr(order.FiatToUSDRate), + FeeCryptoToUsdtRate: DecimalToStringPtr(order.FeeCryptoToUSDTRate), + UsdtToCryptoTypeRate: DecimalToStringPtr(order.USDTToCryptoTypeRate), + PaymentFiat: order.PaymentFiat, + PaymentUnitPrice: DecimalToStringPtr(order.PaymentUnitPrice), + PaymentTemplateID: order.PaymentTemplateID, + OrderArrivalTime: order.OrderArrivalTime, + OrderPaymentTime: order.OrderPaymentTime, + UnpaidTimeoutSecond: order.UnpaidTimeoutSecond, + ChainType: order.ChainType, + TxHash: order.TxHash, + FromAddress: order.FromAddress, + ToAddress: order.ToAddress, + ChainFee: DecimalToStringPtr(order.ChainFee), + ChainFeeCrypto: order.ChainFeeCrypto, + Memo: order.Memo, + OrderNote: order.OrderNote, + } + res = append(res, resp) + } + + return res +} diff --git a/internal/usecase/order_test.go b/internal/usecase/order_test.go new file mode 100644 index 0000000..44f1136 --- /dev/null +++ b/internal/usecase/order_test.go @@ -0,0 +1,500 @@ +package usecase + +import ( + "app-cloudep-trade-service/internal/domain" + "app-cloudep-trade-service/internal/domain/usecase" + mockmodel "app-cloudep-trade-service/internal/mock/model" + model "app-cloudep-trade-service/internal/model/mongo" + "context" + "errors" + "time" + + "github.com/go-playground/assert/v2" + "github.com/shopspring/decimal" + "github.com/zeromicro/go-zero/core/stores/mon" + "go.uber.org/mock/gomock" + + "testing" +) + +func TestOrderUseCase_CancelOrder(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // 初始化 mock 依賴 + mockOrderModel := mockmodel.NewMockOrderModel(ctrl) + + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + cancelOrderQuery := usecase.CancelOrderQuery{ + BusinessID: "business_123", + Status: domain.OrderStatusCancelled, // 使用模擬的狀態 + } + + tests := []struct { + name string + mockSetup func() + param usecase.CancelOrderQuery + wantErr bool + }{ + { + name: "successful order cancellation", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ + BusinessID: cancelOrderQuery.BusinessID, + Status: cancelOrderQuery.Status.ToInt64(), + }).Return(nil, nil) + }, + param: cancelOrderQuery, + wantErr: false, + }, + { + name: "failed order cancellation", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ + BusinessID: cancelOrderQuery.BusinessID, + Status: cancelOrderQuery.Status.ToInt64(), + }).Return(nil, errors.New("update error")) + }, + param: cancelOrderQuery, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() // 設置每個測試用例的 mock 行為 + err := orderUseCase.CancelOrder(context.Background(), tt.param) + if (err != nil) != tt.wantErr { + t.Errorf("CancelOrder() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOrderUseCase_CreateOrder(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + // 初始化 mock 依賴 + mockOrderModel := mockmodel.NewMockOrderModel(mockCtrl) + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + now := time.Now().UTC().UnixNano() + + // 構建測試參數 + createOrderReq := usecase.CreateOrderReq{ + BusinessID: "business_123", + OrderType: domain.OrderTypeTest, + OrderStatus: domain.OrderStatusCreated, + Brand: "test_brand", + OrderUID: "user_123", + ReferenceID: "reference_123", + Count: decimal.NewFromInt(2), + OrderFee: decimal.NewFromFloat(0.01), + Amount: decimal.NewFromFloat(100.0), + ReferenceBrand: StringPtr("reference_brand"), + ReferenceUID: StringPtr("reference_uid"), + WalletStatus: 1, + ThreePartyStatus: Int64Ptr(2), + DirectionType: 1, + CryptoType: StringPtr("BTC"), + PaymentFiat: StringPtr("USD"), + PaymentTemplateID: StringPtr("template_001"), + OrderArrivalTime: Int64Ptr(now), + OrderPaymentTime: Int64Ptr(now), + UnpaidTimeoutSecond: Int64Ptr(3600), + ChainType: StringPtr("mainnet"), + TxHash: StringPtr("tx_hash_123"), + FromAddress: StringPtr("from_address"), + ToAddress: StringPtr("to_address"), + ChainFeeCrypto: StringPtr("ETH"), + Memo: StringPtr("test memo"), + OrderNote: StringPtr("test order note"), + } + + tests := []struct { + name string + mockSetup func() + param usecase.CreateOrderReq + wantErr bool + }{ + { + name: "successful order creation", + mockSetup: func() { + mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil) + }, + param: createOrderReq, + wantErr: false, + }, + { + name: "failed order creation", + mockSetup: func() { + mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert error")) + }, + param: createOrderReq, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() // 設置每個測試用例的 mock 行為 + err := orderUseCase.CreateOrder(context.Background(), tt.param) + if (err != nil) != tt.wantErr { + t.Errorf("CreateOrder() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOrderUseCase_DeleteOrder(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // 初始化 mock 依賴 + mockOrderModel := mockmodel.NewMockOrderModel(ctrl) + + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + // 構建測試參數 + deleteOrderQuery := usecase.DeleteOrderQuery{ + BusinessID: "business_123", + } + + tests := []struct { + name string + mockSetup func() + param usecase.DeleteOrderQuery + wantErr bool + }{ + { + name: "successful order deletion", + mockSetup: func() { + mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), deleteOrderQuery.BusinessID).Return(nil, nil) + }, + param: deleteOrderQuery, + wantErr: false, + }, + { + name: "failed order deletion", + mockSetup: func() { + mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), deleteOrderQuery.BusinessID).Return(nil, errors.New("delete error")) + }, + param: deleteOrderQuery, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() // 設置每個測試用例的 mock 行為 + err := orderUseCase.DeleteOrder(context.Background(), tt.param) + if (err != nil) != tt.wantErr { + t.Errorf("DeleteOrder() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOrderUseCase_GetOrder(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + // 初始化 mock 依賴 + mockOrderModel := mockmodel.NewMockOrderModel(mockCtrl) + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + getOrderQuery := usecase.GetOrderQuery{ + BusinessID: "business_123", + } + + // 構建測試用例返回的 Order + mockOrder := &model.Order{ + UpdateTime: 123456789, + CreateTime: 123456789, + BusinessID: "business_123", + OrderType: domain.OrderTypeTest, + OrderStatus: domain.OrderStatusCreated, + Brand: "test_brand", + OrderUID: "user_123", + ReferenceID: "reference_123", + Count: decimal.NewFromInt(2), + OrderFee: decimal.NewFromFloat(0.01), + Amount: decimal.NewFromFloat(100.0), + ReferenceBrand: StringPtr("reference_brand"), + ReferenceUID: StringPtr("reference_uid"), + WalletStatus: 1, + ThreePartyStatus: Int64Ptr(2), + DirectionType: 1, + CryptoType: StringPtr("BTC"), + PaymentFiat: StringPtr("USD"), + PaymentTemplateID: StringPtr("template_001"), + OrderArrivalTime: Int64Ptr(123456789), + OrderPaymentTime: Int64Ptr(123456789), + UnpaidTimeoutSecond: Int64Ptr(3600), + ChainType: StringPtr("mainnet"), + TxHash: StringPtr("tx_hash_123"), + FromAddress: StringPtr("from_address"), + ToAddress: StringPtr("to_address"), + ChainFeeCrypto: StringPtr("ETH"), + Memo: StringPtr("test memo"), + OrderNote: StringPtr("test order note"), + } + + tests := []struct { + name string + mockSetup func() + param usecase.GetOrderQuery + wantResp *usecase.GetOrderResp + wantErr bool + }{ + { + name: "successful order retrieval", + mockSetup: func() { + mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), getOrderQuery.BusinessID).Return(mockOrder, nil) + }, + param: getOrderQuery, + wantResp: &usecase.GetOrderResp{ // 构建预期响应 + UpdateTime: mockOrder.UpdateTime, + CreateTime: mockOrder.CreateTime, + BusinessID: mockOrder.BusinessID, + OrderType: mockOrder.OrderType, + OrderStatus: mockOrder.OrderStatus, + Brand: mockOrder.Brand, + OrderUID: mockOrder.OrderUID, + ReferenceID: mockOrder.ReferenceID, + Count: mockOrder.Count.String(), + OrderFee: mockOrder.OrderFee.String(), + Amount: mockOrder.Amount.String(), + ReferenceBrand: mockOrder.ReferenceBrand, + ReferenceUID: mockOrder.ReferenceUID, + WalletStatus: Int64Ptr(mockOrder.WalletStatus), + ThreePartyStatus: mockOrder.ThreePartyStatus, + DirectionType: Int64Ptr(mockOrder.DirectionType), + CryptoType: mockOrder.CryptoType, + PaymentFiat: mockOrder.PaymentFiat, + PaymentTemplateID: mockOrder.PaymentTemplateID, + OrderArrivalTime: mockOrder.OrderArrivalTime, + OrderPaymentTime: mockOrder.OrderPaymentTime, + UnpaidTimeoutSecond: mockOrder.UnpaidTimeoutSecond, + ChainType: mockOrder.ChainType, + TxHash: mockOrder.TxHash, + FromAddress: mockOrder.FromAddress, + ToAddress: mockOrder.ToAddress, + ChainFeeCrypto: mockOrder.ChainFeeCrypto, + Memo: mockOrder.Memo, + OrderNote: mockOrder.OrderNote, + }, + wantErr: false, + }, + { + name: "order not found", + mockSetup: func() { + mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), getOrderQuery.BusinessID).Return(nil, mon.ErrNotFound) + }, + param: getOrderQuery, + wantResp: nil, + wantErr: true, + }, + { + name: "database error", + mockSetup: func() { + mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), getOrderQuery.BusinessID).Return(nil, errors.New("database error")) + }, + param: getOrderQuery, + wantResp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() // 設置每個測試用例的 mock 行為 + resp, err := orderUseCase.GetOrder(context.Background(), tt.param) + + if (err != nil) != tt.wantErr { + t.Errorf("GetOrder() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantResp, resp) + }) + } +} + +func TestOrderUseCase_ListOrder(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockOrderModel := mockmodel.NewMockOrderModel(mockCtrl) + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + listOrderQuery := usecase.GetOrderListReq{ + PageIndex: 1, + PageSize: 10, + BusinessID: "business_123", + OrderStatus: []int64{int64(domain.OrderStatusCreated)}, + } + + mockOrders := []model.Order{ + { + BusinessID: "business_123", + OrderUID: "user_123", + OrderStatus: domain.OrderStatusCreated, + // 其他欄位根據需要填充 + }, + } + mockTotal := int64(1) + + tests := []struct { + name string + mockSetup func() + param usecase.GetOrderListReq + wantResp *usecase.ListOrderResp + wantErr bool + }{ + { + name: "successful order listing", + mockSetup: func() { + mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return(mockOrders, mockTotal, nil) + }, + param: listOrderQuery, + wantResp: &usecase.ListOrderResp{ + Data: orderUseCase.convertOrdersToResponses(mockOrders), + Page: &usecase.Pager{ + Total: mockTotal, + Index: listOrderQuery.PageIndex, + Size: listOrderQuery.PageSize, + }, + }, + wantErr: false, + }, + { + name: "order listing error", + mockSetup: func() { + mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("listing error")) + }, + param: listOrderQuery, + wantResp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() + resp, err := orderUseCase.ListOrder(context.Background(), tt.param) + if (err != nil) != tt.wantErr { + t.Errorf("ListOrder() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantResp, resp) + }) + } +} + +func TestOrderUseCase_ModifyOrderStatus(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockOrderModel := mockmodel.NewMockOrderModel(mockCtrl) + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + modifyOrderQuery := &usecase.ModifyOrderQuery{ + BusinessID: "business_123", + Status: int64(domain.OrderStatusCancelled), + } + + tests := []struct { + name string + mockSetup func() + param *usecase.ModifyOrderQuery + wantErr bool + }{ + { + name: "successful order status modification", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ + BusinessID: modifyOrderQuery.BusinessID, + Status: modifyOrderQuery.Status, + }).Return(nil, nil) + }, + param: modifyOrderQuery, + wantErr: false, + }, + { + name: "order status modification error", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ + BusinessID: modifyOrderQuery.BusinessID, + Status: modifyOrderQuery.Status, + }).Return(nil, errors.New("update error")) + }, + param: modifyOrderQuery, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() + err := orderUseCase.ModifyOrderStatus(context.Background(), tt.param) + if (err != nil) != tt.wantErr { + t.Errorf("ModifyOrderStatus() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOrderUseCase_OrderStatusTimeout(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockOrderModel := mockmodel.NewMockOrderModel(mockCtrl) + orderUseCase := &OrderUseCase{ + OrderModel: mockOrderModel, + } + + tests := []struct { + name string + mockSetup func() + wantErr bool + }{ + { + name: "successful timeout update", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, nil) + }, + wantErr: false, + }, + { + name: "timeout update error", + mockSetup: func() { + mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, errors.New("timeout error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockSetup() + err := orderUseCase.OrderStatusTimeout(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("OrderStatusTimeout() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/usecase/utils.go b/internal/usecase/utils.go new file mode 100644 index 0000000..cd31945 --- /dev/null +++ b/internal/usecase/utils.go @@ -0,0 +1,31 @@ +package usecase + +import "github.com/shopspring/decimal" + +// Int64Ptr 將 int64 類型轉為 *int64,若值為 0 則返回 nil +func Int64Ptr(val int64) *int64 { + if val == 0 { + return nil + } + + return &val +} + +// DecimalToStringPtr 將 decimal.Decimal 指標轉換為 *string,若為 nil 則返回 nil +func DecimalToStringPtr(val *decimal.Decimal) *string { + if val == nil { + return nil + } + str := val.String() + + return &str +} + +// StringPtr 將 *string 或其他欄位直接返回(適合用於已是指標的欄位) +func StringPtr(val string) *string { + if val == "" { + return nil + } + + return &val +} diff --git a/internal/usecase/utils_test.go b/internal/usecase/utils_test.go new file mode 100644 index 0000000..f18c5a1 --- /dev/null +++ b/internal/usecase/utils_test.go @@ -0,0 +1,89 @@ +package usecase + +import ( + "testing" + + "github.com/go-playground/assert/v2" + "github.com/shopspring/decimal" +) + +// 測試 Int64Ptr 函數 +func TestInt64Ptr(t *testing.T) { + tests := []struct { + name string + val int64 + want *int64 + }{ + { + name: "non-zero value", + val: 123, + want: func() *int64 { v := int64(123); return &v }(), + }, + { + name: "zero value", + val: 0, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Int64Ptr(tt.val) + assert.Equal(t, tt.want, got) + }) + } +} + +// 測試 DecimalToStringPtr 函數 +func TestDecimalToStringPtr(t *testing.T) { + tests := []struct { + name string + val *decimal.Decimal + want *string + }{ + { + name: "non-nil decimal", + val: func() *decimal.Decimal { d := decimal.NewFromFloat(123.45); return &d }(), + want: func() *string { s := "123.45"; return &s }(), + }, + { + name: "nil decimal", + val: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := DecimalToStringPtr(tt.val) + assert.Equal(t, tt.want, got) + }) + } +} + +// 測試 StringPtr 函數 +func TestStringPtr(t *testing.T) { + tests := []struct { + name string + val string + want *string + }{ + { + name: "non-empty string", + val: "test", + want: func() *string { s := "test"; return &s }(), + }, + { + name: "empty string", + val: "", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := StringPtr(tt.val) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/trade.go b/trade.go new file mode 100644 index 0000000..6f262a4 --- /dev/null +++ b/trade.go @@ -0,0 +1,50 @@ +package main + +import ( + "flag" + + "github.com/zeromicro/go-zero/core/logx" + + "app-cloudep-trade-service/gen_result/pb/trade" + "app-cloudep-trade-service/internal/config" + couponserviceServer "app-cloudep-trade-service/internal/server/couponservice" + inventoryserviceServer "app-cloudep-trade-service/internal/server/inventoryservice" + orderserviceServer "app-cloudep-trade-service/internal/server/orderservice" + productserviceServer "app-cloudep-trade-service/internal/server/productservice" + subscriptionserviceServer "app-cloudep-trade-service/internal/server/subscriptionservice" + walletserviceServer "app-cloudep-trade-service/internal/server/walletservice" + "app-cloudep-trade-service/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/core/service" + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var configFile = flag.String("f", "etc/trade.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + ctx := svc.NewServiceContext(c) + + s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { + trade.RegisterOrderServiceServer(grpcServer, orderserviceServer.NewOrderServiceServer(ctx)) + trade.RegisterWalletServiceServer(grpcServer, walletserviceServer.NewWalletServiceServer(ctx)) + trade.RegisterInventoryServiceServer(grpcServer, inventoryserviceServer.NewInventoryServiceServer(ctx)) + trade.RegisterProductServiceServer(grpcServer, productserviceServer.NewProductServiceServer(ctx)) + trade.RegisterSubscriptionServiceServer(grpcServer, subscriptionserviceServer.NewSubscriptionServiceServer(ctx)) + trade.RegisterCouponServiceServer(grpcServer, couponserviceServer.NewCouponServiceServer(ctx)) + + if c.Mode == service.DevMode || c.Mode == service.TestMode { + reflection.Register(grpcServer) + } + }) + defer s.Stop() + + logx.Infof("Starting rpc server at %s...\n", c.ListenOn) + s.Start() +}