feature/order_base #1
			
				
			
		
		
		
	
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -61,4 +61,4 @@ mock-gen: # 建立 mock 資料 | ||||||
| 
 | 
 | ||||||
| .PHONY: migrate-database | .PHONY: migrate-database | ||||||
| migrate-database: | migrate-database: | ||||||
| 	migrate -source file://generate/database/migrations/mongodb -database 'mongodb://127.0.0.1:27017/digimon_order' up | 	migrate -source file://generate/database/mongodb -database 'mongodb://127.0.0.1:27017/digimon_order' up | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | use digimon_order; | ||||||
|  | 
 | ||||||
|  | db.order.createIndex({ "business_id": 1, "order_status": 1, "create_time": -1 }) | ||||||
|  | db.orders.createIndex({ "reference_id": 1, "reference_uid": 1, "business_id": 1, "uid": 1 }) | ||||||
|  | db.orders.createIndex({ "order_type": 1, "direction_type": 1, "order_status": 1 }) | ||||||
|  | db.orders.createIndex({ "create_time": -1, "update_time": -1, "order_arrival_time": -1, "order_payment_time": -1 }) | ||||||
|  | 
 | ||||||
|  | db.order.createIndex({ | ||||||
|  |     "uid": 1, | ||||||
|  |     "order_type": 1, | ||||||
|  |     "order_status": 1, | ||||||
|  |     "create_time": -1, | ||||||
|  | }) | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| syntax = "proto3"; | syntax = "proto3"; | ||||||
| package tweeting; | package order; | ||||||
| option go_package = "./tweeting"; | option go_package = "./order"; | ||||||
| 
 | 
 | ||||||
| // ========== 基本回應 =========== | // ========== 基本回應 =========== | ||||||
| message OKResp {} | message OKResp {} | ||||||
|  | @ -9,38 +9,149 @@ message OKResp {} | ||||||
| message NoneReq {} | message NoneReq {} | ||||||
| 
 | 
 | ||||||
| // 分頁信息 | // 分頁信息 | ||||||
| message Pager | message Pager { | ||||||
| { |  | ||||||
|   int64 total = 1;  // 總數量 |   int64 total = 1;  // 總數量 | ||||||
|   int64 size = 2;   // 每頁數量 |   int64 size = 2;   // 每頁數量 | ||||||
|   int64 index = 3;  // 當前頁碼 |   int64 index = 3;  // 當前頁碼 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| message CreateOrderReq{} | message CreateOrderReq { | ||||||
| message CreateOrderResp{} |   string business_id = 1;   // 訂單業務流水號 | ||||||
|  |   int32 order_type = 2;     // 訂單類型 | ||||||
|  |   int32 order_status = 3;   // 訂單狀態 | ||||||
|  |   string brand = 4;         // 下單平台 | ||||||
|  |   string order_uid = 5;     // 下單用戶 UID | ||||||
|  |   string reference_id = 6;  // 訂單來源 | ||||||
|  |   string count = 7;         // 訂單數量 (decimal to string) | ||||||
|  |   string order_fee = 8;     // 訂單手續費 (decimal to string) | ||||||
|  |   string amount = 9;        // 單價 (decimal to string) | ||||||
| 
 | 
 | ||||||
| message CancelOrderReq{} |   // 以下是可選字段(擴充用) | ||||||
| message ModifyOrderReq{} |   optional string reference_brand = 10;           // 訂單來源平台 | ||||||
| message ModifyOrderStatusReq{} |   optional string reference_uid = 11;             // 訂單來源用戶 UID | ||||||
| message DeleteOrderReq{} |   optional int64 wallet_status = 12;              // 交易金額狀態 | ||||||
|  |   optional int64 three_party_status = 13;         // 三方請求狀態 | ||||||
|  |   optional int64 direction_type = 14;             // 交易方向 | ||||||
|  |   optional string crypto_type = 15;               // 交易幣種 | ||||||
|  |   optional string third_party_fee = 16;           // 第三方手續費 (decimal to string) | ||||||
|  |   optional string crypto_to_usdt_rate = 17;       // 交易幣種對 USDT 匯率 (decimal to string) | ||||||
|  |   optional string fiat_to_usd_rate = 18;          // 法幣對 USD 匯率 (decimal to string) | ||||||
|  |   optional string fee_crypto_to_usdt_rate = 19;   // 手續費幣種對 USDT 匯率 (decimal to string) | ||||||
|  |   optional string usdt_to_crypto_type_rate = 20;  // USDT 對交易幣種匯率 (decimal to string) | ||||||
|  |   optional string payment_fiat = 21;              // 支付法幣 | ||||||
|  |   optional string payment_unit_price = 22;        // crypto 單價 (decimal to string) | ||||||
|  |   optional string payment_template_id = 23;       // 支付方式配置 ID | ||||||
|  |   optional int64 order_arrival_time = 24;         // 訂單到帳時間 | ||||||
|  |   optional int64 order_payment_time = 25;         // 訂單付款時間 | ||||||
|  |   optional int64 unpaid_timeout_second = 26;      // 支付期限秒數 | ||||||
|  |   optional string chain_type = 27;                // 主網類型 | ||||||
|  |   optional string tx_hash = 28;                   // 交易哈希 | ||||||
|  |   optional string from_address = 29;              // 來源地址 | ||||||
|  |   optional string to_address = 30;                // 目標地址 | ||||||
|  |   optional string chain_fee = 31;                 // 鏈上交易手續費 (decimal to string) | ||||||
|  |   optional string chain_fee_crypto = 32;          // 鏈上手續費使用幣別 | ||||||
|  |   optional string memo = 33;                      // 鏈上備註 | ||||||
|  |   optional string order_note = 34;                // 訂單交易備註 | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| message GetOrderReq{} | message CancelOrderReq { | ||||||
| message GetOrderResp{} |   string business_id = 1; | ||||||
|  |   int64 status = 2; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| message ListOrderReq{} | message ModifyOrderStatusReq { | ||||||
| message ListOrderResp{} |   string business_id = 1; | ||||||
|  |   int64 status = 2; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | message DeleteOrderReq { | ||||||
|  |   string business_id = 1; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| message OrderStatusTimeoutReq {} | message OrderStatusTimeoutReq {} | ||||||
| 
 | 
 | ||||||
|  | message GetOrderReq { | ||||||
|  |   string business_id = 1;  // 訂單業務流水號 | ||||||
|  | } | ||||||
|  | message GetOrderResp { | ||||||
|  |   string business_id = 1;   // 訂單業務流水號 | ||||||
|  |   int32 order_type = 2;     // 訂單類型 | ||||||
|  |   int32 order_status = 3;   // 訂單狀態 | ||||||
|  |   string brand = 4;         // 下單平台 | ||||||
|  |   string order_uid = 5;     // 下單用戶 UID | ||||||
|  |   string reference_id = 6;  // 訂單來源 | ||||||
|  |   string count = 7;         // 訂單數量 (decimal to string) | ||||||
|  |   string order_fee = 8;     // 訂單手續費 (decimal to string) | ||||||
|  |   string amount = 9;        // 單價 (decimal to string) | ||||||
|  | 
 | ||||||
|  |   // 以下是可選字段(擴充用) | ||||||
|  |   optional string reference_brand = 10;           // 訂單來源平台 | ||||||
|  |   optional string reference_uid = 11;             // 訂單來源用戶 UID | ||||||
|  |   optional int64 wallet_status = 12;              // 交易金額狀態 | ||||||
|  |   optional int64 three_party_status = 13;         // 三方請求狀態 | ||||||
|  |   optional int64 direction_type = 14;             // 交易方向 | ||||||
|  |   optional string crypto_type = 15;               // 交易幣種 | ||||||
|  |   optional string third_party_fee = 16;           // 第三方手續費 (decimal to string) | ||||||
|  |   optional string crypto_to_usdt_rate = 17;       // 交易幣種對 USDT 匯率 (decimal to string) | ||||||
|  |   optional string fiat_to_usd_rate = 18;          // 法幣對 USD 匯率 (decimal to string) | ||||||
|  |   optional string fee_crypto_to_usdt_rate = 19;   // 手續費幣種對 USDT 匯率 (decimal to string) | ||||||
|  |   optional string usdt_to_crypto_type_rate = 20;  // USDT 對交易幣種匯率 (decimal to string) | ||||||
|  |   optional string payment_fiat = 21;              // 支付法幣 | ||||||
|  |   optional string payment_unit_price = 22;        // crypto 單價 (decimal to string) | ||||||
|  |   optional string payment_template_id = 23;       // 支付方式配置 ID | ||||||
|  |   optional int64 order_arrival_time = 24;         // 訂單到帳時間 | ||||||
|  |   optional int64 order_payment_time = 25;         // 訂單付款時間 | ||||||
|  |   optional int64 unpaid_timeout_second = 26;      // 支付期限秒數 | ||||||
|  |   optional string chain_type = 27;                // 主網類型 | ||||||
|  |   optional string tx_hash = 28;                   // 交易哈希 | ||||||
|  |   optional string from_address = 29;              // 來源地址 | ||||||
|  |   optional string to_address = 30;                // 目標地址 | ||||||
|  |   optional string chain_fee = 31;                 // 鏈上交易手續費 (decimal to string) | ||||||
|  |   optional string chain_fee_crypto = 32;          // 鏈上手續費使用幣別 | ||||||
|  |   optional string memo = 33;                      // 鏈上備註 | ||||||
|  |   optional string order_note = 34;                // 訂單交易備註 | ||||||
|  |   int64 create_time = 35;                      // 建立時間 | ||||||
|  |   int64 update_time = 36;                     // 更新時間 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ModifyOrderReq {} | ||||||
|  | 
 | ||||||
|  | message ListOrderReq { | ||||||
|  |   int64 page_index = 1; // required | ||||||
|  |   int64 page_size = 2;  // required | ||||||
|  | 
 | ||||||
|  |   string reference_id = 3; | ||||||
|  |   string reference_uid = 4; | ||||||
|  |   string business_id = 5; | ||||||
|  |   string uid = 6; | ||||||
|  |   int32 order_type = 7; | ||||||
|  |   repeated int32 direction_type = 8; | ||||||
|  |   repeated int32 order_status = 9; | ||||||
|  | 
 | ||||||
|  |   int64 start_create_time = 10; | ||||||
|  |   int64 end_create_time = 11; | ||||||
|  |   int64 start_update_time = 12; | ||||||
|  |   int64 end_update_time = 13; | ||||||
|  |   int64 start_order_arrival_time = 14; | ||||||
|  |   int64 end_order_arrival_time = 15; | ||||||
|  |   int64 start_order_payment_time = 16; | ||||||
|  |   int64 end_order_payment_time = 17; | ||||||
|  | 
 | ||||||
|  |   string crypto_type = 18; | ||||||
|  |   string tx_hash = 19; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListOrderResp { | ||||||
|  |   repeated GetOrderResp data = 1; // 訂單列表 | ||||||
|  |   Pager page = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // OrderService 訂單服務(業務邏輯在外面組合) | // OrderService 訂單服務(業務邏輯在外面組合) | ||||||
| service OrderService { | service OrderService { | ||||||
|   // CreateOrder 建立訂單 |   // CreateOrder 建立訂單 | ||||||
|   rpc CreateOrder(CreateOrderReq)returns(CreateOrderResp); |   rpc CreateOrder(CreateOrderReq) returns (OKResp); | ||||||
|   // CancelOrder 取消訂單 |   // CancelOrder 取消訂單 | ||||||
|   rpc CancelOrder(CancelOrderReq) returns (OKResp); |   rpc CancelOrder(CancelOrderReq) returns (OKResp); | ||||||
|   // ModifyOrder 修改訂單 |  | ||||||
|   rpc ModifyOrder(ModifyOrderReq)returns(OKResp); |  | ||||||
|   // ModifyOrderStatus 修改訂單狀態 |   // ModifyOrderStatus 修改訂單狀態 | ||||||
|   rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp); |   rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp); | ||||||
|   // DeleteOrder 刪除訂單(軟刪除) |   // DeleteOrder 刪除訂單(軟刪除) | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										20
									
								
								go.mod
								
								
								
								
							|  | @ -3,7 +3,14 @@ module app-cloudep-order-server | ||||||
| go 1.22.3 | go 1.22.3 | ||||||
| 
 | 
 | ||||||
| require ( | 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/validator/v10 v10.22.0 | ||||||
|  | 	github.com/shopspring/decimal v1.4.0 | ||||||
|  | 	github.com/stretchr/testify v1.9.0 | ||||||
| 	github.com/zeromicro/go-zero v1.7.2 | 	github.com/zeromicro/go-zero v1.7.2 | ||||||
|  | 	go.mongodb.org/mongo-driver v1.16.1 | ||||||
|  | 	go.uber.org/mock v0.4.0 | ||||||
| 	google.golang.org/grpc v1.67.1 | 	google.golang.org/grpc v1.67.1 | ||||||
| 	google.golang.org/protobuf v1.34.2 | 	google.golang.org/protobuf v1.34.2 | ||||||
| ) | ) | ||||||
|  | @ -18,14 +25,18 @@ require ( | ||||||
| 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||||||
| 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect | 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect | ||||||
| 	github.com/fatih/color v1.17.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/logr v1.4.2 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
| 	github.com/go-openapi/jsonpointer v0.19.6 // indirect | 	github.com/go-openapi/jsonpointer v0.19.6 // indirect | ||||||
| 	github.com/go-openapi/jsonreference v0.20.2 // indirect | 	github.com/go-openapi/jsonreference v0.20.2 // indirect | ||||||
| 	github.com/go-openapi/swag v0.22.4 // 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/gogo/protobuf v1.3.2 // indirect | ||||||
| 	github.com/golang/mock v1.6.0 // indirect | 	github.com/golang/mock v1.6.0 // indirect | ||||||
| 	github.com/golang/protobuf v1.5.4 // 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/gnostic-models v0.6.8 // indirect | ||||||
| 	github.com/google/go-cmp v0.6.0 // indirect | 	github.com/google/go-cmp v0.6.0 // indirect | ||||||
| 	github.com/google/gofuzz v1.2.0 // indirect | 	github.com/google/gofuzz v1.2.0 // indirect | ||||||
|  | @ -34,20 +45,27 @@ require ( | ||||||
| 	github.com/josharian/intern v1.0.0 // indirect | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.9 // 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/mailru/easyjson v0.7.7 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||||
| 	github.com/openzipkin/zipkin-go v0.4.3 // indirect | 	github.com/openzipkin/zipkin-go v0.4.3 // indirect | ||||||
| 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect | 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect | ||||||
|  | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/prometheus/client_golang v1.20.2 // indirect | 	github.com/prometheus/client_golang v1.20.2 // indirect | ||||||
| 	github.com/prometheus/client_model v0.6.1 // indirect | 	github.com/prometheus/client_model v0.6.1 // indirect | ||||||
| 	github.com/prometheus/common v0.55.0 // indirect | 	github.com/prometheus/common v0.55.0 // indirect | ||||||
| 	github.com/prometheus/procfs v0.15.1 // indirect | 	github.com/prometheus/procfs v0.15.1 // indirect | ||||||
| 	github.com/redis/go-redis/v9 v9.6.1 // indirect | 	github.com/redis/go-redis/v9 v9.6.1 // indirect | ||||||
| 	github.com/spaolacci/murmur3 v1.1.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-20201027041543-1326539a0a0a // indirect | ||||||
| 	go.etcd.io/etcd/api/v3 v3.5.15 // 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/pkg/v3 v3.5.15 // indirect | ||||||
| 	go.etcd.io/etcd/client/v3 v3.5.15 // indirect | 	go.etcd.io/etcd/client/v3 v3.5.15 // indirect | ||||||
|  | @ -66,8 +84,10 @@ require ( | ||||||
| 	go.uber.org/automaxprocs v1.5.3 // indirect | 	go.uber.org/automaxprocs v1.5.3 // indirect | ||||||
| 	go.uber.org/multierr v1.9.0 // indirect | 	go.uber.org/multierr v1.9.0 // indirect | ||||||
| 	go.uber.org/zap v1.24.0 // indirect | 	go.uber.org/zap v1.24.0 // indirect | ||||||
|  | 	golang.org/x/crypto v0.26.0 // indirect | ||||||
| 	golang.org/x/net v0.28.0 // indirect | 	golang.org/x/net v0.28.0 // indirect | ||||||
| 	golang.org/x/oauth2 v0.22.0 // indirect | 	golang.org/x/oauth2 v0.22.0 // indirect | ||||||
|  | 	golang.org/x/sync v0.8.0 // indirect | ||||||
| 	golang.org/x/sys v0.24.0 // indirect | 	golang.org/x/sys v0.24.0 // indirect | ||||||
| 	golang.org/x/term v0.23.0 // indirect | 	golang.org/x/term v0.23.0 // indirect | ||||||
| 	golang.org/x/text v0.17.0 // indirect | 	golang.org/x/text v0.17.0 // indirect | ||||||
|  |  | ||||||
|  | @ -4,4 +4,13 @@ import "github.com/zeromicro/go-zero/zrpc" | ||||||
| 
 | 
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	zrpc.RpcServerConf | 	zrpc.RpcServerConf | ||||||
|  | 
 | ||||||
|  | 	Mongo struct { | ||||||
|  | 		Schema   string | ||||||
|  | 		User     string | ||||||
|  | 		Password string | ||||||
|  | 		Host     string | ||||||
|  | 		Port     string | ||||||
|  | 		Database string | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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, " ")) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | package domain | ||||||
|  | 
 | ||||||
|  | type OrderStatus int64 | ||||||
|  | 
 | ||||||
|  | func (o *OrderStatus) ToInt() int { | ||||||
|  | 	return int(*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 // 交易超時
 | ||||||
|  | ) | ||||||
|  | @ -1,9 +1,13 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	"app-cloudep-order-server/internal/domain" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
|  | 
 | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | @ -23,9 +27,42 @@ func NewCancelOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cance | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CancelOrder 取消訂單
 | // CancelOrderQuery 1.建單失敗 9.交易取消 10.交易異常
 | ||||||
| func (l *CancelOrderLogic) CancelOrder(in *tweeting.CancelOrderReq) (*tweeting.OKResp, error) { | type CancelOrderQuery struct { | ||||||
| 	// todo: add your logic here and delete this line
 | 	BusinessID string `json:"business_id" validate:"required"` | ||||||
| 
 | 	Status     int64  `json:"status" validate:"required,oneof=1 9 10"` | ||||||
| 	return &tweeting.OKResp{}, nil | } | ||||||
|  | 
 | ||||||
|  | // CancelOrder 取消訂單
 | ||||||
|  | func (l *CancelOrderLogic) CancelOrder(in *order.CancelOrderReq) (*order.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.OrderModel.UpdateStatus(l.ctx, | ||||||
|  | 		model.UpdateStatusReq{ | ||||||
|  | 			BusinessID: in.GetBusinessId(), | ||||||
|  | 			Status:     in.GetStatus(), | ||||||
|  | 		}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// 錯誤代碼 06-021-02
 | ||||||
|  | 		e := domain.CommentErrorL( | ||||||
|  | 			domain.CancelOrderErrorCode, | ||||||
|  | 			logx.WithContext(l.ctx), | ||||||
|  | 			[]logx.LogField{ | ||||||
|  | 				{Key: "req", Value: in}, | ||||||
|  | 				{Key: "func", Value: "OrderModel.UpdateStatus"}, | ||||||
|  | 				{Key: "err", Value: err}, | ||||||
|  | 			}, | ||||||
|  | 			"failed to update order status:").Wrap(err) | ||||||
|  | 
 | ||||||
|  | 		return nil, e | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.OKResp{}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,110 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mocksvc "app-cloudep-order-server/internal/mock/lib" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | 
 | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestCancelOrder(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 	mockValidate := mocksvc.NewMockValidate(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 		Validate:   mockValidate, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據
 | ||||||
|  | 	cancelReq := &order.CancelOrderReq{ | ||||||
|  | 		BusinessId: "12345", | ||||||
|  | 		Status:     1, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		input     *order.CancelOrderReq | ||||||
|  | 		prepare   func() | ||||||
|  | 		expectErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:  "成功取消訂單", | ||||||
|  | 			input: cancelReq, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證通過
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬更新狀態成功
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ | ||||||
|  | 					BusinessID: "12345", | ||||||
|  | 					Status:     1, | ||||||
|  | 				}).Return(&mongo.UpdateResult{MatchedCount: 1}, nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:  "訂單更新狀態失敗", | ||||||
|  | 			input: cancelReq, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證通過
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬更新狀態失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{ | ||||||
|  | 					BusinessID: "12345", | ||||||
|  | 					Status:     1, | ||||||
|  | 				}).Return(nil, errors.New("update failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:  "驗證失敗", | ||||||
|  | 			input: cancelReq, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證失敗
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid input")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 執行測試
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 設置測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 CancelOrderLogic
 | ||||||
|  | 			logic := CancelOrderLogic{ | ||||||
|  | 				svcCtx: svcCtx, | ||||||
|  | 				ctx:    context.TODO(), | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// 執行 CancelOrder
 | ||||||
|  | 			resp, err := logic.CancelOrder(tt.input) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.NotNil(t, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,9 +1,15 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	"app-cloudep-order-server/internal/domain" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | @ -23,9 +29,226 @@ func NewCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Creat | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateOrder 建立訂單
 | type createOrderReq struct { // 訂單ID
 | ||||||
| func (l *CreateOrderLogic) CreateOrder(in *tweeting.CreateOrderReq) (*tweeting.CreateOrderResp, error) { | 	BusinessID           string           `json:"business_id" validate:"required"`                                      // 訂單業務流水號
 | ||||||
| 	// todo: add your logic here and delete this line
 | 	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"`              // 訂單狀態
 | ||||||
| 	return &tweeting.CreateOrderResp{}, nil | 	Brand                string           `json:"brand" validate:"required"`                                            // 下單平台
 | ||||||
|  | 	OrderUID             string           `json:"order_uid" validate:"required"`                                        // 下單用戶 UID
 | ||||||
|  | 	ReferenceID          string           `json:"reference_id" validate:"required"`                                     // 訂單來源
 | ||||||
|  | 	Count                decimal.Decimal  `json:"count" validate:"required,decimalGt=0"`                                // 訂單數量
 | ||||||
|  | 	OrderFee             decimal.Decimal  `json:"order_fee" validate:"required,decimalGte=0"`                           // 訂單手續費
 | ||||||
|  | 	Amount               decimal.Decimal  `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        *decimal.Decimal `json:"third_party_fee,omitempty" validate:"omitempty,decimalGte=0"`          // 第三方手續費
 | ||||||
|  | 	CryptoToUSDTRate     *decimal.Decimal `json:"crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"`      // 交易幣種對 USDT 匯率
 | ||||||
|  | 	FiatToUSDRate        *decimal.Decimal `json:"fiat_to_usd_rate,omitempty" validate:"omitempty,decimalGte=0"`         // 法幣對 USD 匯率
 | ||||||
|  | 	FeeCryptoToUSDTRate  *decimal.Decimal `json:"fee_crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"`  // 手續費幣種對 USDT 匯率
 | ||||||
|  | 	USDTToCryptoTypeRate *decimal.Decimal `json:"usdt_to_crypto_type_rate,omitempty" validate:"omitempty,decimalGte=0"` // USDT 對交易幣種匯率
 | ||||||
|  | 	PaymentFiat          *string          `json:"payment_fiat,omitempty" validate:"omitempty"`                          // 支付法幣
 | ||||||
|  | 	PaymentUnitPrice     *decimal.Decimal `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"`                    // 訂單到帳時間
 | ||||||
|  | 	OrderPaymentTime     *int64           `json:"order_payment_time,omitempty" validate:"omitempty"`                    // 訂單付款時間
 | ||||||
|  | 	UnpaidTimeoutSecond  *int64           `json:"unpaid_timeout_second,omitempty" validate:"omitempty,decimalGte=0"`    // 支付期限秒數
 | ||||||
|  | 	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             *decimal.Decimal `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"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreateOrder 建立訂單
 | ||||||
|  | func (l *CreateOrderLogic) CreateOrder(in *order.CreateOrderReq) (*order.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()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	now := time.Now().UTC().UnixNano() | ||||||
|  | 	// 插入資料庫
 | ||||||
|  | 	o := &model.Order{ | ||||||
|  | 		UpdateTime:  now, | ||||||
|  | 		CreateTime:  now, | ||||||
|  | 		BusinessID:  req.BusinessID, | ||||||
|  | 		OrderType:   req.OrderType, | ||||||
|  | 		OrderStatus: req.OrderStatus, | ||||||
|  | 		Brand:       req.Brand, | ||||||
|  | 		OrderUID:    req.OrderUID, | ||||||
|  | 		ReferenceID: req.ReferenceID, | ||||||
|  | 		Count:       convertDecimalToDecimal128(req.Count), | ||||||
|  | 		OrderFee:    convertDecimalToDecimal128(req.OrderFee), | ||||||
|  | 		Amount:      convertDecimalToDecimal128(req.Amount), | ||||||
|  | 		// 下面的是未來擴充用,加密貨幣用,或者幣別轉換用,普通訂單用不到
 | ||||||
|  | 		ReferenceBrand:       req.ReferenceBrand, | ||||||
|  | 		ReferenceUID:         req.ReferenceUID, | ||||||
|  | 		WalletStatus:         req.WalletStatus, | ||||||
|  | 		ThreePartyStatus:     req.ThreePartyStatus, | ||||||
|  | 		DirectionType:        req.DirectionType, | ||||||
|  | 		CryptoType:           req.CryptoType, | ||||||
|  | 		ThirdPartyFee:        convertDecimalPtrToDecimal128(req.ThirdPartyFee), | ||||||
|  | 		CryptoToUSDTRate:     convertDecimalPtrToDecimal128(req.CryptoToUSDTRate), | ||||||
|  | 		FiatToUSDRate:        convertDecimalPtrToDecimal128(req.FiatToUSDRate), | ||||||
|  | 		FeeCryptoToUSDTRate:  convertDecimalPtrToDecimal128(req.FeeCryptoToUSDTRate), | ||||||
|  | 		USDTToCryptoTypeRate: convertDecimalPtrToDecimal128(req.USDTToCryptoTypeRate), | ||||||
|  | 		PaymentFiat:          req.PaymentFiat, | ||||||
|  | 		PaymentUnitPrice:     convertDecimalPtrToDecimal128(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:             convertDecimalPtrToDecimal128(req.ChainFee), | ||||||
|  | 		ChainFeeCrypto:       req.ChainFeeCrypto, | ||||||
|  | 		Memo:                 req.Memo, | ||||||
|  | 		OrderNote:            req.OrderNote, | ||||||
|  | 	} | ||||||
|  | 	err = l.svcCtx.OrderModel.Insert(l.ctx, o) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		// 錯誤代碼 06-021-01
 | ||||||
|  | 		e := domain.CommentErrorL( | ||||||
|  | 			domain.CreateOrderErrorCode, | ||||||
|  | 			logx.WithContext(l.ctx), | ||||||
|  | 			[]logx.LogField{ | ||||||
|  | 				{Key: "req", Value: in}, | ||||||
|  | 				{Key: "func", Value: "OrderModel.Insert"}, | ||||||
|  | 				{Key: "err", Value: err}, | ||||||
|  | 			}, | ||||||
|  | 			"failed to insert order into mongo:").Wrap(err) | ||||||
|  | 
 | ||||||
|  | 		return nil, e | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.OKResp{}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //nolint:gocyclo,gocognit
 | ||||||
|  | func buildCreateOrderReq(in *order.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, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	// 只有當 Count, OrderFee, Amount 不為空時才進行轉換和設置
 | ||||||
|  | 	if in.Count != "" { | ||||||
|  | 		createOrderReq.Count, err = decimal.NewFromString(in.Count) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.OrderFee != "" { | ||||||
|  | 		createOrderReq.OrderFee, err = decimal.NewFromString(in.OrderFee) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.Amount != "" { | ||||||
|  | 		createOrderReq.Amount, err = decimal.NewFromString(in.Amount) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 判斷可選字段,只有不為 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 = decimalPtrFromString(*in.ThirdPartyFee) | ||||||
|  | 	} | ||||||
|  | 	if in.CryptoToUsdtRate != nil && *in.CryptoToUsdtRate != "" { | ||||||
|  | 		createOrderReq.CryptoToUSDTRate = decimalPtrFromString(*in.CryptoToUsdtRate) | ||||||
|  | 	} | ||||||
|  | 	if in.FiatToUsdRate != nil && *in.FiatToUsdRate != "" { | ||||||
|  | 		createOrderReq.FiatToUSDRate = decimalPtrFromString(*in.FiatToUsdRate) | ||||||
|  | 	} | ||||||
|  | 	if in.FeeCryptoToUsdtRate != nil && *in.FeeCryptoToUsdtRate != "" { | ||||||
|  | 		createOrderReq.FeeCryptoToUSDTRate = decimalPtrFromString(*in.FeeCryptoToUsdtRate) | ||||||
|  | 	} | ||||||
|  | 	if in.UsdtToCryptoTypeRate != nil && *in.UsdtToCryptoTypeRate != "" { | ||||||
|  | 		createOrderReq.USDTToCryptoTypeRate = decimalPtrFromString(*in.UsdtToCryptoTypeRate) | ||||||
|  | 	} | ||||||
|  | 	if in.PaymentFiat != nil { | ||||||
|  | 		createOrderReq.PaymentFiat = in.PaymentFiat | ||||||
|  | 	} | ||||||
|  | 	if in.PaymentUnitPrice != nil && *in.PaymentUnitPrice != "" { | ||||||
|  | 		createOrderReq.PaymentUnitPrice = decimalPtrFromString(*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 = decimalPtrFromString(*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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,371 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mocksvc "app-cloudep-order-server/internal/mock/lib" | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestBuildCreateOrderReq(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		input     *order.CreateOrderReq | ||||||
|  | 		expectErr bool | ||||||
|  | 		expected  *createOrderReq | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "所有必填字段存在,可選字段為空", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 			expected: &createOrderReq{ | ||||||
|  | 				BusinessID:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUID:    "UID123", | ||||||
|  | 				ReferenceID: "REF123", | ||||||
|  | 				Count:       decimal.RequireFromString("10.5"), | ||||||
|  | 				OrderFee:    decimal.RequireFromString("0.5"), | ||||||
|  | 				Amount:      decimal.RequireFromString("100"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "包含可選字段", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:       "B123", | ||||||
|  | 				OrderType:        1, | ||||||
|  | 				OrderStatus:      2, | ||||||
|  | 				Brand:            "TestBrand", | ||||||
|  | 				OrderUid:         "UID123", | ||||||
|  | 				ReferenceId:      "REF123", | ||||||
|  | 				Count:            "10.5", | ||||||
|  | 				OrderFee:         "0.5", | ||||||
|  | 				Amount:           "100", | ||||||
|  | 				ReferenceBrand:   ptr("OtherBrand"), | ||||||
|  | 				WalletStatus:     ptrInt64(1), | ||||||
|  | 				ThreePartyStatus: ptrInt64(2), | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 			expected: &createOrderReq{ | ||||||
|  | 				BusinessID:       "B123", | ||||||
|  | 				OrderType:        1, | ||||||
|  | 				OrderStatus:      2, | ||||||
|  | 				Brand:            "TestBrand", | ||||||
|  | 				OrderUID:         "UID123", | ||||||
|  | 				ReferenceID:      "REF123", | ||||||
|  | 				Count:            decimal.RequireFromString("10.5"), | ||||||
|  | 				OrderFee:         decimal.RequireFromString("0.5"), | ||||||
|  | 				Amount:           decimal.RequireFromString("100"), | ||||||
|  | 				ReferenceBrand:   ptr("OtherBrand"), | ||||||
|  | 				WalletStatus:     ptrInt64(1), | ||||||
|  | 				ThreePartyStatus: ptrInt64(2), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "無效的十進制字段", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "invalid-decimal", // 無效的數值
 | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "空字符串的可選欄位應返回 nil", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:    "B123", | ||||||
|  | 				OrderType:     1, | ||||||
|  | 				OrderStatus:   2, | ||||||
|  | 				Brand:         "TestBrand", | ||||||
|  | 				OrderUid:      "UID123", | ||||||
|  | 				ReferenceId:   "REF123", | ||||||
|  | 				Count:         "10.5", | ||||||
|  | 				OrderFee:      "0.5", | ||||||
|  | 				Amount:        "100", | ||||||
|  | 				ThirdPartyFee: ptr(""), // 空字符串
 | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 			expected: &createOrderReq{ | ||||||
|  | 				BusinessID:    "B123", | ||||||
|  | 				OrderType:     1, | ||||||
|  | 				OrderStatus:   2, | ||||||
|  | 				Brand:         "TestBrand", | ||||||
|  | 				OrderUID:      "UID123", | ||||||
|  | 				ReferenceID:   "REF123", | ||||||
|  | 				Count:         decimal.RequireFromString("10.5"), | ||||||
|  | 				OrderFee:      decimal.RequireFromString("0.5"), | ||||||
|  | 				Amount:        decimal.RequireFromString("100"), | ||||||
|  | 				ThirdPartyFee: nil, // 空字符串應返回 nil
 | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			result, err := buildCreateOrderReq(tt.input) | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Equal(t, tt.expected, result) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestCreateOrder(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 	mockValidate := mocksvc.NewMockValidate(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 		Validate:   mockValidate, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		input     *order.CreateOrderReq | ||||||
|  | 		prepare   func() | ||||||
|  | 		expectErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "成功建立訂單", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫插入成功
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "建立訂單時驗證失敗", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證失敗
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "插入資料庫失敗", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬插入資料庫失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "成功建立訂單,必填字段", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫插入成功
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "成功建立訂單,可選字段有值", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:       "B123", | ||||||
|  | 				OrderType:        1, | ||||||
|  | 				OrderStatus:      2, | ||||||
|  | 				Brand:            "TestBrand", | ||||||
|  | 				OrderUid:         "UID123", | ||||||
|  | 				ReferenceId:      "REF123", | ||||||
|  | 				Count:            "10.5", | ||||||
|  | 				OrderFee:         "0.5", | ||||||
|  | 				Amount:           "100", | ||||||
|  | 				ReferenceBrand:   ptr("OtherBrand"), | ||||||
|  | 				WalletStatus:     ptrInt64(1), | ||||||
|  | 				CryptoType:       ptr("BTC"), | ||||||
|  | 				PaymentFiat:      ptr("USD"), | ||||||
|  | 				OrderArrivalTime: ptrInt64(time.Now().UTC().Unix()), | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫插入成功
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "部分可選字段有值,其他為空", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:     "B123", | ||||||
|  | 				OrderType:      1, | ||||||
|  | 				OrderStatus:    2, | ||||||
|  | 				Brand:          "TestBrand", | ||||||
|  | 				OrderUid:       "UID123", | ||||||
|  | 				ReferenceId:    "REF123", | ||||||
|  | 				Count:          "10.5", | ||||||
|  | 				OrderFee:       "0.5", | ||||||
|  | 				Amount:         "100", | ||||||
|  | 				ReferenceBrand: ptr("OtherBrand"), | ||||||
|  | 				WalletStatus:   ptrInt64(1), | ||||||
|  | 				CryptoType:     nil, // 空
 | ||||||
|  | 				PaymentFiat:    nil, // 空
 | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫插入成功
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "無效的十進制數字", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "invalid", // 無效的數值
 | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 無需調用資料庫操作,因為會在驗證或轉換時失敗
 | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "可選欄位為空字符串", | ||||||
|  | 			input: &order.CreateOrderReq{ | ||||||
|  | 				BusinessId:    "B123", | ||||||
|  | 				OrderType:     1, | ||||||
|  | 				OrderStatus:   2, | ||||||
|  | 				Brand:         "TestBrand", | ||||||
|  | 				OrderUid:      "UID123", | ||||||
|  | 				ReferenceId:   "REF123", | ||||||
|  | 				Count:         "10.5", | ||||||
|  | 				OrderFee:      "0.5", | ||||||
|  | 				Amount:        "100", | ||||||
|  | 				ThirdPartyFee: ptr(""), // 空字符串
 | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫插入成功
 | ||||||
|  | 				mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 設置測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 CreateOrderLogic
 | ||||||
|  | 			logic := NewCreateOrderLogic(context.TODO(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 			// 執行 CreateOrder
 | ||||||
|  | 			resp, err := logic.CreateOrder(tt.input) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.NotNil(t, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ================================ 輔助函數 ================================
 | ||||||
|  | func ptr(s string) *string { | ||||||
|  | 	return &s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ptrInt64(i int64) *int64 { | ||||||
|  | 	return &i | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // decimalPtr 是一個輔助函數,用來將字串轉換為 *decimal.Decimal 指針
 | ||||||
|  | func decimalPtr(val string) *decimal.Decimal { | ||||||
|  | 	d, _ := decimal.NewFromString(val) | ||||||
|  | 	return &d | ||||||
|  | } | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Helper function for decimal conversion with nil support and logging
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Convert decimal.Decimal to primitive.Decimal128 and handle nil values
 | ||||||
|  | func convertDecimalPtrToDecimal128(d *decimal.Decimal) *primitive.Decimal128 { | ||||||
|  | 	if d == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	result, err := primitive.ParseDecimal128(d.String()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		logx.Errorf("Failed to convert decimal to Decimal128: %v", err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Convert decimal.Decimal (non-pointer) to primitive.Decimal128 and log errors
 | ||||||
|  | func convertDecimalToDecimal128(d decimal.Decimal) primitive.Decimal128 { | ||||||
|  | 	result, err := primitive.ParseDecimal128(d.String()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		logx.Errorf("Failed to convert decimal to Decimal128: %v", err) | ||||||
|  | 		return primitive.NewDecimal128(0, 0) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 將 primitive.Decimal128 轉換為字符串
 | ||||||
|  | func convertDecimal128ToString(d128 *primitive.Decimal128) *string { | ||||||
|  | 	if d128 == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	result := d128.String() | ||||||
|  | 
 | ||||||
|  | 	return &result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Helper functions for optional fields
 | ||||||
|  | func optionalString(s *string) *string { | ||||||
|  | 	if s != nil { | ||||||
|  | 		return s | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func optionalInt64(i *int64) *int64 { | ||||||
|  | 	if i != nil { | ||||||
|  | 		return i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func optionalDecimalToString(d *decimal.Decimal) *string { | ||||||
|  | 	if d != nil { | ||||||
|  | 		s := d.String() | ||||||
|  | 
 | ||||||
|  | 		return &s | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func nilString(s *string) *string { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func nilInt64(i *int64) *int64 { | ||||||
|  | 	if i == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
|  | 
 | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | @ -23,9 +25,25 @@ func NewDeleteOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delet | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeleteOrder 刪除訂單(軟刪除)
 | // DeleteOrderQuery 刪除訂單(軟刪除)
 | ||||||
| func (l *DeleteOrderLogic) DeleteOrder(in *tweeting.DeleteOrderReq) (*tweeting.OKResp, error) { | type DeleteOrderQuery struct { | ||||||
| 	// todo: add your logic here and delete this line
 | 	BusinessID string `json:"business_id" validate:"required"` | ||||||
| 
 | } | ||||||
| 	return &tweeting.OKResp{}, nil | 
 | ||||||
|  | // DeleteOrder 刪除訂單(軟刪除)
 | ||||||
|  | func (l *DeleteOrderLogic) DeleteOrder(in *order.DeleteOrderReq) (*order.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.OrderModel.DeleteByBusinessID(l.ctx, in.GetBusinessId()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.OKResp{}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,97 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mocksvc "app-cloudep-order-server/internal/mock/lib" | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestDeleteOrder(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 	mockValidate := mocksvc.NewMockValidate(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 		Validate:   mockValidate, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		input     *order.DeleteOrderReq | ||||||
|  | 		prepare   func() | ||||||
|  | 		expectErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "成功刪除訂單", | ||||||
|  | 			input: &order.DeleteOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫刪除成功
 | ||||||
|  | 				mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "驗證失敗", | ||||||
|  | 			input: &order.DeleteOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證失敗
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "刪除資料庫失敗", | ||||||
|  | 			input: &order.DeleteOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬資料庫刪除失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, errors.New("delete failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 設置測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 DeleteOrderLogic
 | ||||||
|  | 			logic := NewDeleteOrderLogic(context.TODO(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 			// 執行 DeleteOrder
 | ||||||
|  | 			resp, err := logic.DeleteOrder(tt.input) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.NotNil(t, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,10 +1,13 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
| 
 | 	"app-cloudep-order-server/internal/domain" | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"github.com/zeromicro/go-zero/core/stores/mon" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
| ) | ) | ||||||
|  | @ -23,9 +26,66 @@ func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrder | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetOrder 取得訂單詳情
 | // GetOrderQuery 取得訂單
 | ||||||
| func (l *GetOrderLogic) GetOrder(in *tweeting.GetOrderReq) (*tweeting.GetOrderResp, error) { | type GetOrderQuery struct { | ||||||
| 	// todo: add your logic here and delete this line
 | 	BusinessID string `json:"business_id" validate:"required"` | ||||||
| 
 | } | ||||||
| 	return &tweeting.GetOrderResp{}, nil | 
 | ||||||
|  | // GetOrder 取得訂單詳情
 | ||||||
|  | func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.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.OrderModel.FindOneBusinessID(l.ctx, in.GetBusinessId()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.As(err, &mon.ErrNotFound) { | ||||||
|  | 			return nil, domain.NotFoundError(domain.DataNotFoundErrorCode, "failed to get this order id:", in.GetBusinessId()) | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.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.String(), | ||||||
|  | 		OrderFee:    o.OrderFee.String(), | ||||||
|  | 		Amount:      o.Amount.String(), | ||||||
|  | 		// 下面的為未來擴充用的欄位
 | ||||||
|  | 		ReferenceBrand:       nilString(o.ReferenceBrand), | ||||||
|  | 		ReferenceUid:         nilString(o.ReferenceUID), | ||||||
|  | 		WalletStatus:         nilInt64(o.WalletStatus), | ||||||
|  | 		ThreePartyStatus:     nilInt64(o.ThreePartyStatus), | ||||||
|  | 		DirectionType:        nilInt64(o.DirectionType), | ||||||
|  | 		CryptoType:           nilString(o.CryptoType), | ||||||
|  | 		ThirdPartyFee:        convertDecimal128ToString(o.ThirdPartyFee), | ||||||
|  | 		CryptoToUsdtRate:     convertDecimal128ToString(o.CryptoToUSDTRate), | ||||||
|  | 		FiatToUsdRate:        convertDecimal128ToString(o.FiatToUSDRate), | ||||||
|  | 		FeeCryptoToUsdtRate:  convertDecimal128ToString(o.FeeCryptoToUSDTRate), | ||||||
|  | 		UsdtToCryptoTypeRate: convertDecimal128ToString(o.USDTToCryptoTypeRate), | ||||||
|  | 		PaymentFiat:          nilString(o.PaymentFiat), | ||||||
|  | 		PaymentUnitPrice:     convertDecimal128ToString(o.PaymentUnitPrice), | ||||||
|  | 		PaymentTemplateId:    nilString(o.PaymentTemplateID), | ||||||
|  | 		OrderArrivalTime:     nilInt64(o.OrderArrivalTime), | ||||||
|  | 		OrderPaymentTime:     nilInt64(o.OrderPaymentTime), | ||||||
|  | 		UnpaidTimeoutSecond:  nilInt64(o.UnpaidTimeoutSecond), | ||||||
|  | 		ChainType:            nilString(o.ChainType), | ||||||
|  | 		TxHash:               nilString(o.TxHash), | ||||||
|  | 		FromAddress:          nilString(o.FromAddress), | ||||||
|  | 		ToAddress:            nilString(o.ToAddress), | ||||||
|  | 		ChainFee:             convertDecimal128ToString(o.ChainFee), | ||||||
|  | 		ChainFeeCrypto:       nilString(o.ChainFeeCrypto), | ||||||
|  | 		Memo:                 nilString(o.Memo), | ||||||
|  | 		OrderNote:            nilString(o.OrderNote), | ||||||
|  | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,131 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mocksvc "app-cloudep-order-server/internal/mock/lib" | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestGetOrder(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 	mockValidate := mocksvc.NewMockValidate(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 		Validate:   mockValidate, | ||||||
|  | 	} | ||||||
|  | 	Count, _ := primitive.ParseDecimal128("10.5") | ||||||
|  | 	OrderFee, _ := primitive.ParseDecimal128("0.5") | ||||||
|  | 	Amount, _ := primitive.ParseDecimal128("100") | ||||||
|  | 
 | ||||||
|  | 	// 模擬返回的訂單數據
 | ||||||
|  | 	mockOrder := &model.Order{ | ||||||
|  | 		UpdateTime:  1630000000, | ||||||
|  | 		CreateTime:  1620000000, | ||||||
|  | 		BusinessID:  "B123", | ||||||
|  | 		OrderType:   1, | ||||||
|  | 		OrderStatus: 2, | ||||||
|  | 		Brand:       "TestBrand", | ||||||
|  | 		OrderUID:    "UID123", | ||||||
|  | 		ReferenceID: "REF123", | ||||||
|  | 		Count:       Count, | ||||||
|  | 		OrderFee:    OrderFee, | ||||||
|  | 		Amount:      Amount, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name      string | ||||||
|  | 		input     *order.GetOrderReq | ||||||
|  | 		prepare   func() | ||||||
|  | 		expectErr bool | ||||||
|  | 		expected  *order.GetOrderResp | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "成功取得訂單", | ||||||
|  | 			input: &order.GetOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬查詢訂單成功
 | ||||||
|  | 				mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(mockOrder, nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 			expected: &order.GetOrderResp{ | ||||||
|  | 				UpdateTime:  1630000000, | ||||||
|  | 				CreateTime:  1620000000, | ||||||
|  | 				BusinessId:  "B123", | ||||||
|  | 				OrderType:   1, | ||||||
|  | 				OrderStatus: 2, | ||||||
|  | 				Brand:       "TestBrand", | ||||||
|  | 				OrderUid:    "UID123", | ||||||
|  | 				ReferenceId: "REF123", | ||||||
|  | 				Count:       "10.5", | ||||||
|  | 				OrderFee:    "0.5", | ||||||
|  | 				Amount:      "100", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "驗證失敗", | ||||||
|  | 			input: &order.GetOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證失敗
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "查詢訂單失敗", | ||||||
|  | 			input: &order.GetOrderReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬查詢訂單失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(nil, errors.New("order not found")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 設置測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 GetOrderLogic
 | ||||||
|  | 			logic := NewGetOrderLogic(context.TODO(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 			// 執行 GetOrder
 | ||||||
|  | 			resp, err := logic.GetOrder(tt.input) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Equal(t, tt.expected, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
| ) | ) | ||||||
|  | @ -23,9 +25,125 @@ func NewListOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListOrd | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ListOrder 取得訂單列表
 | type GetOrderListReq struct { | ||||||
| func (l *ListOrderLogic) ListOrder(in *tweeting.ListOrderReq) (*tweeting.ListOrderResp, error) { | 	PageIndex int64 `json:"page_index" validate:"required"` | ||||||
| 	// todo: add your logic here and delete this line
 | 	PageSize  int64 `json:"page_size" validate:"required"` | ||||||
| 
 | 
 | ||||||
| 	return &tweeting.ListOrderResp{}, nil | 	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"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ListOrder 取得訂單列表
 | ||||||
|  | func (l *ListOrderLogic) ListOrder(in *order.ListOrderReq) (*order.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()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 構建查詢條件
 | ||||||
|  | 	req := model.GetOrderListReq{ | ||||||
|  | 		PageIndex:             in.GetPageIndex(), | ||||||
|  | 		PageSize:              in.GetPageSize(), | ||||||
|  | 		ReferenceID:           in.ReferenceId, | ||||||
|  | 		ReferenceUID:          in.ReferenceUid, | ||||||
|  | 		BusinessID:            in.BusinessId, | ||||||
|  | 		UID:                   in.Uid, | ||||||
|  | 		OrderType:             int(in.OrderType), | ||||||
|  | 		DirectionType:         in.DirectionType, | ||||||
|  | 		OrderStatus:           in.OrderStatus, | ||||||
|  | 		StartCreateTime:       in.StartCreateTime, | ||||||
|  | 		EndCreateTime:         in.EndCreateTime, | ||||||
|  | 		StartUpdateTime:       in.StartUpdateTime, | ||||||
|  | 		EndUpdateTime:         in.EndUpdateTime, | ||||||
|  | 		StartOrderArrivalTime: in.StartOrderArrivalTime, | ||||||
|  | 		EndOrderArrivalTime:   in.EndOrderArrivalTime, | ||||||
|  | 		StartOrderPaymentTime: in.StartOrderPaymentTime, | ||||||
|  | 		EndOrderPaymentTime:   in.EndOrderPaymentTime, | ||||||
|  | 		CryptoType:            in.CryptoType, | ||||||
|  | 		TxHash:                in.TxHash, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 查詢訂單
 | ||||||
|  | 	orders, total, err := l.svcCtx.OrderModel.ListOrder(l.ctx, req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 構建返回結果
 | ||||||
|  | 	res := make([]*order.GetOrderResp, 0, len(orders)) | ||||||
|  | 	for _, item := range orders { | ||||||
|  | 		res = append(res, convertOrderToGetOrderResp(item)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.ListOrderResp{ | ||||||
|  | 		Data: res, | ||||||
|  | 		Page: &order.Pager{ | ||||||
|  | 			Total: total, | ||||||
|  | 			Index: in.GetPageIndex(), | ||||||
|  | 			Size:  in.GetPageSize(), | ||||||
|  | 		}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConvertOrderToGetOrderResp 將 Order 結構轉換為 GetOrderResp
 | ||||||
|  | func convertOrderToGetOrderResp(o model.Order) *order.GetOrderResp { | ||||||
|  | 	return &order.GetOrderResp{ | ||||||
|  | 		BusinessId:           o.BusinessID, | ||||||
|  | 		OrderType:            int32(o.OrderType), | ||||||
|  | 		OrderStatus:          int32(o.OrderStatus), | ||||||
|  | 		Brand:                o.Brand, | ||||||
|  | 		OrderUid:             o.OrderUID, | ||||||
|  | 		ReferenceId:          o.ReferenceID, | ||||||
|  | 		Count:                o.Count.String(), | ||||||
|  | 		OrderFee:             o.OrderFee.String(), | ||||||
|  | 		Amount:               o.Amount.String(), | ||||||
|  | 		ReferenceBrand:       optionalString(o.ReferenceBrand), | ||||||
|  | 		ReferenceUid:         optionalString(o.ReferenceUID), | ||||||
|  | 		WalletStatus:         optionalInt64(o.WalletStatus), | ||||||
|  | 		ThreePartyStatus:     optionalInt64(o.ThreePartyStatus), | ||||||
|  | 		DirectionType:        optionalInt64(o.DirectionType), | ||||||
|  | 		CryptoType:           optionalString(o.CryptoType), | ||||||
|  | 		ThirdPartyFee:        convertDecimal128ToString(o.ThirdPartyFee), | ||||||
|  | 		CryptoToUsdtRate:     convertDecimal128ToString(o.CryptoToUSDTRate), | ||||||
|  | 		FiatToUsdRate:        convertDecimal128ToString(o.FiatToUSDRate), | ||||||
|  | 		FeeCryptoToUsdtRate:  convertDecimal128ToString(o.FeeCryptoToUSDTRate), | ||||||
|  | 		UsdtToCryptoTypeRate: convertDecimal128ToString(o.USDTToCryptoTypeRate), | ||||||
|  | 		PaymentFiat:          optionalString(o.PaymentFiat), | ||||||
|  | 		PaymentUnitPrice:     convertDecimal128ToString(o.PaymentUnitPrice), | ||||||
|  | 		PaymentTemplateId:    optionalString(o.PaymentTemplateID), | ||||||
|  | 		OrderArrivalTime:     optionalInt64(o.OrderArrivalTime), | ||||||
|  | 		OrderPaymentTime:     optionalInt64(o.OrderPaymentTime), | ||||||
|  | 		UnpaidTimeoutSecond:  optionalInt64(o.UnpaidTimeoutSecond), | ||||||
|  | 		ChainType:            optionalString(o.ChainType), | ||||||
|  | 		TxHash:               optionalString(o.TxHash), | ||||||
|  | 		FromAddress:          optionalString(o.FromAddress), | ||||||
|  | 		ToAddress:            optionalString(o.ToAddress), | ||||||
|  | 		ChainFee:             convertDecimal128ToString(o.ChainFee), | ||||||
|  | 		ChainFeeCrypto:       optionalString(o.ChainFeeCrypto), | ||||||
|  | 		Memo:                 optionalString(o.Memo), | ||||||
|  | 		OrderNote:            optionalString(o.OrderNote), | ||||||
|  | 		CreateTime:           o.CreateTime, | ||||||
|  | 		UpdateTime:           o.UpdateTime, | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,444 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | // func TestConvertOrdersToGetOrderResp(t *testing.T) {
 | ||||||
|  | // 	tests := []struct {
 | ||||||
|  | // 		name     string
 | ||||||
|  | // 		input    []model.Order
 | ||||||
|  | // 		expected []*order.GetOrderResp
 | ||||||
|  | // 	}{
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name:     "空訂單列表",
 | ||||||
|  | // 			input:    []model.Order{},
 | ||||||
|  | // 			expected: []*order.GetOrderResp{},
 | ||||||
|  | // 		},
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "單筆訂單",
 | ||||||
|  | // 			input: []model.Order{
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessID:  "B123",
 | ||||||
|  | // 					OrderType:   1,
 | ||||||
|  | // 					OrderStatus: 2,
 | ||||||
|  | // 					Brand:       "TestBrand",
 | ||||||
|  | // 					OrderUID:    "UID123",
 | ||||||
|  | // 					ReferenceID: "REF123",
 | ||||||
|  | // 					Count:       decimal.RequireFromString("10.5"),
 | ||||||
|  | // 					OrderFee:    decimal.RequireFromString("0.5"),
 | ||||||
|  | // 					Amount:      decimal.RequireFromString("100"),
 | ||||||
|  | // 				},
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expected: []*order.GetOrderResp{
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessId:  "B123",
 | ||||||
|  | // 					OrderType:   1,
 | ||||||
|  | // 					OrderStatus: 2,
 | ||||||
|  | // 					Brand:       "TestBrand",
 | ||||||
|  | // 					OrderUid:    "UID123",
 | ||||||
|  | // 					ReferenceId: "REF123",
 | ||||||
|  | // 					Count:       "10.5",
 | ||||||
|  | // 					OrderFee:    "0.5",
 | ||||||
|  | // 					Amount:      "100",
 | ||||||
|  | // 				},
 | ||||||
|  | // 			},
 | ||||||
|  | // 		},
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "多筆訂單",
 | ||||||
|  | // 			input: []model.Order{
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessID:  "B123",
 | ||||||
|  | // 					OrderType:   1,
 | ||||||
|  | // 					OrderStatus: 2,
 | ||||||
|  | // 					Brand:       "TestBrand",
 | ||||||
|  | // 					OrderUID:    "UID123",
 | ||||||
|  | // 					ReferenceID: "REF123",
 | ||||||
|  | // 					Count:       decimal.RequireFromString("10.5"),
 | ||||||
|  | // 					OrderFee:    decimal.RequireFromString("0.5"),
 | ||||||
|  | // 					Amount:      decimal.RequireFromString("100"),
 | ||||||
|  | // 				},
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessID:  "B456",
 | ||||||
|  | // 					OrderType:   2,
 | ||||||
|  | // 					OrderStatus: 3,
 | ||||||
|  | // 					Brand:       "OtherBrand",
 | ||||||
|  | // 					OrderUID:    "UID456",
 | ||||||
|  | // 					ReferenceID: "REF456",
 | ||||||
|  | // 					Count:       decimal.RequireFromString("20"),
 | ||||||
|  | // 					OrderFee:    decimal.RequireFromString("1"),
 | ||||||
|  | // 					Amount:      decimal.RequireFromString("200"),
 | ||||||
|  | // 				},
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expected: []*order.GetOrderResp{
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessId:  "B123",
 | ||||||
|  | // 					OrderType:   1,
 | ||||||
|  | // 					OrderStatus: 2,
 | ||||||
|  | // 					Brand:       "TestBrand",
 | ||||||
|  | // 					OrderUid:    "UID123",
 | ||||||
|  | // 					ReferenceId: "REF123",
 | ||||||
|  | // 					Count:       "10.5",
 | ||||||
|  | // 					OrderFee:    "0.5",
 | ||||||
|  | // 					Amount:      "100",
 | ||||||
|  | // 				},
 | ||||||
|  | // 				{
 | ||||||
|  | // 					BusinessId:  "B456",
 | ||||||
|  | // 					OrderType:   2,
 | ||||||
|  | // 					OrderStatus: 3,
 | ||||||
|  | // 					Brand:       "OtherBrand",
 | ||||||
|  | // 					OrderUid:    "UID456",
 | ||||||
|  | // 					ReferenceId: "REF456",
 | ||||||
|  | // 					Count:       "20",
 | ||||||
|  | // 					OrderFee:    "1",
 | ||||||
|  | // 					Amount:      "200",
 | ||||||
|  | // 				},
 | ||||||
|  | // 			},
 | ||||||
|  | // 		},
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // 	for _, tt := range tests {
 | ||||||
|  | // 		t.Run(tt.name, func(t *testing.T) {
 | ||||||
|  | // 			// 執行 ConvertOrdersToGetOrderResp 函數
 | ||||||
|  | // 			result := ConvertOrdersToGetOrderResp(tt.input)
 | ||||||
|  | //
 | ||||||
|  | // 			// 驗證結果
 | ||||||
|  | // 			assert.Equal(t, tt.expected, result)
 | ||||||
|  | // 		})
 | ||||||
|  | // 	}
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // func TestOptionalString(t *testing.T) {
 | ||||||
|  | // 	t.Run("非 nil 字串", func(t *testing.T) {
 | ||||||
|  | // 		str := "hello"
 | ||||||
|  | // 		result := optionalString(&str)
 | ||||||
|  | // 		assert.NotNil(t, result)
 | ||||||
|  | // 		assert.Equal(t, &str, result)
 | ||||||
|  | // 	})
 | ||||||
|  | //
 | ||||||
|  | // 	t.Run("nil 字串", func(t *testing.T) {
 | ||||||
|  | // 		result := optionalString(nil)
 | ||||||
|  | // 		assert.Nil(t, result)
 | ||||||
|  | // 	})
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // func TestOptionalInt64(t *testing.T) {
 | ||||||
|  | // 	t.Run("非 nil int64", func(t *testing.T) {
 | ||||||
|  | // 		num := int64(123)
 | ||||||
|  | // 		result := optionalInt64(&num)
 | ||||||
|  | // 		assert.NotNil(t, result)
 | ||||||
|  | // 		assert.Equal(t, &num, result)
 | ||||||
|  | // 	})
 | ||||||
|  | //
 | ||||||
|  | // 	t.Run("nil int64", func(t *testing.T) {
 | ||||||
|  | // 		result := optionalInt64(nil)
 | ||||||
|  | // 		assert.Nil(t, result)
 | ||||||
|  | // 	})
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // func TestOptionalDecimalToString(t *testing.T) {
 | ||||||
|  | // 	t.Run("非 nil decimal", func(t *testing.T) {
 | ||||||
|  | // 		dec := decimal.NewFromInt(123)
 | ||||||
|  | // 		expected := "123"
 | ||||||
|  | // 		result := optionalDecimalToString(&dec)
 | ||||||
|  | // 		assert.NotNil(t, result)
 | ||||||
|  | // 		assert.Equal(t, &expected, result)
 | ||||||
|  | // 	})
 | ||||||
|  | //
 | ||||||
|  | // 	t.Run("nil decimal", func(t *testing.T) {
 | ||||||
|  | // 		result := optionalDecimalToString(nil)
 | ||||||
|  | // 		assert.Nil(t, result)
 | ||||||
|  | // 	})
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // func TestConvertOrderToGetOrderResp(t *testing.T) {
 | ||||||
|  | // 	tests := []struct {
 | ||||||
|  | // 		name     string
 | ||||||
|  | // 		input    model.Order
 | ||||||
|  | // 		expected *order.GetOrderResp
 | ||||||
|  | // 	}{
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "所有欄位都有值",
 | ||||||
|  | // 			input: model.Order{
 | ||||||
|  | // 				BusinessID:           "B123",
 | ||||||
|  | // 				OrderType:            1,
 | ||||||
|  | // 				OrderStatus:          2,
 | ||||||
|  | // 				Brand:                "TestBrand",
 | ||||||
|  | // 				OrderUID:             "UID123",
 | ||||||
|  | // 				ReferenceID:          "REF123",
 | ||||||
|  | // 				Count:                decimal.RequireFromString("10.5"),
 | ||||||
|  | // 				OrderFee:             decimal.RequireFromString("0.5"),
 | ||||||
|  | // 				Amount:               decimal.RequireFromString("100"),
 | ||||||
|  | // 				ReferenceBrand:       ptrString("OtherBrand"),
 | ||||||
|  | // 				ReferenceUID:         ptrString("REFUID"),
 | ||||||
|  | // 				WalletStatus:         ptrInt64(1),
 | ||||||
|  | // 				ThreePartyStatus:     ptrInt64(2),
 | ||||||
|  | // 				DirectionType:        ptrInt64(1),
 | ||||||
|  | // 				CryptoType:           ptrString("BTC"),
 | ||||||
|  | // 				ThirdPartyFee:        decimalPtrFromString("0.01"),
 | ||||||
|  | // 				CryptoToUSDTRate:     decimalPtrFromString("50000"),
 | ||||||
|  | // 				FiatToUSDRate:        decimalPtrFromString("1"),
 | ||||||
|  | // 				FeeCryptoToUSDTRate:  decimalPtrFromString("0.02"),
 | ||||||
|  | // 				USDTToCryptoTypeRate: decimalPtrFromString("0.00002"),
 | ||||||
|  | // 				PaymentFiat:          ptrString("USD"),
 | ||||||
|  | // 				PaymentUnitPrice:     decimalPtrFromString("50000"),
 | ||||||
|  | // 				PaymentTemplateID:    ptrString("TEMPLATE123"),
 | ||||||
|  | // 				OrderArrivalTime:     ptrInt64(1630000000),
 | ||||||
|  | // 				OrderPaymentTime:     ptrInt64(1620000000),
 | ||||||
|  | // 				UnpaidTimeoutSecond:  ptrInt64(3600),
 | ||||||
|  | // 				ChainType:            ptrString("ETH"),
 | ||||||
|  | // 				TxHash:               ptrString("0xABC123"),
 | ||||||
|  | // 				FromAddress:          ptrString("0xFROM123"),
 | ||||||
|  | // 				ToAddress:            ptrString("0xTO123"),
 | ||||||
|  | // 				ChainFee:             decimalPtrFromString("0.001"),
 | ||||||
|  | // 				ChainFeeCrypto:       ptrString("ETH"),
 | ||||||
|  | // 				Memo:                 ptrString("Test Memo"),
 | ||||||
|  | // 				OrderNote:            ptrString("Test Note"),
 | ||||||
|  | // 				CreateTime:           1620000000,
 | ||||||
|  | // 				UpdateTime:           1630000000,
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expected: &order.GetOrderResp{
 | ||||||
|  | // 				BusinessId:           "B123",
 | ||||||
|  | // 				OrderType:            1,
 | ||||||
|  | // 				OrderStatus:          2,
 | ||||||
|  | // 				Brand:                "TestBrand",
 | ||||||
|  | // 				OrderUid:             "UID123",
 | ||||||
|  | // 				ReferenceId:          "REF123",
 | ||||||
|  | // 				Count:                "10.5",
 | ||||||
|  | // 				OrderFee:             "0.5",
 | ||||||
|  | // 				Amount:               "100",
 | ||||||
|  | // 				ReferenceBrand:       ptrString("OtherBrand"),
 | ||||||
|  | // 				ReferenceUid:         ptrString("REFUID"),
 | ||||||
|  | // 				WalletStatus:         ptrInt64(1),
 | ||||||
|  | // 				ThreePartyStatus:     ptrInt64(2),
 | ||||||
|  | // 				DirectionType:        ptrInt64(1),
 | ||||||
|  | // 				CryptoType:           ptrString("BTC"),
 | ||||||
|  | // 				ThirdPartyFee:        ptrString("0.01"),
 | ||||||
|  | // 				CryptoToUsdtRate:     ptrString("50000"),
 | ||||||
|  | // 				FiatToUsdRate:        ptrString("1"),
 | ||||||
|  | // 				FeeCryptoToUsdtRate:  ptrString("0.02"),
 | ||||||
|  | // 				UsdtToCryptoTypeRate: ptrString("0.00002"),
 | ||||||
|  | // 				PaymentFiat:          ptrString("USD"),
 | ||||||
|  | // 				PaymentUnitPrice:     ptrString("50000"),
 | ||||||
|  | // 				PaymentTemplateId:    ptrString("TEMPLATE123"),
 | ||||||
|  | // 				OrderArrivalTime:     ptrInt64(1630000000),
 | ||||||
|  | // 				OrderPaymentTime:     ptrInt64(1620000000),
 | ||||||
|  | // 				UnpaidTimeoutSecond:  ptrInt64(3600),
 | ||||||
|  | // 				ChainType:            ptrString("ETH"),
 | ||||||
|  | // 				TxHash:               ptrString("0xABC123"),
 | ||||||
|  | // 				FromAddress:          ptrString("0xFROM123"),
 | ||||||
|  | // 				ToAddress:            ptrString("0xTO123"),
 | ||||||
|  | // 				ChainFee:             ptrString("0.001"),
 | ||||||
|  | // 				ChainFeeCrypto:       ptrString("ETH"),
 | ||||||
|  | // 				Memo:                 ptrString("Test Memo"),
 | ||||||
|  | // 				OrderNote:            ptrString("Test Note"),
 | ||||||
|  | // 				CreateTime:           1620000000,
 | ||||||
|  | // 				UpdateTime:           1630000000,
 | ||||||
|  | // 			},
 | ||||||
|  | // 		},
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "部分欄位為 nil",
 | ||||||
|  | // 			input: model.Order{
 | ||||||
|  | // 				BusinessID:           "B456",
 | ||||||
|  | // 				OrderType:            1,
 | ||||||
|  | // 				OrderStatus:          2,
 | ||||||
|  | // 				Brand:                "TestBrand",
 | ||||||
|  | // 				OrderUID:             "UID456",
 | ||||||
|  | // 				ReferenceID:          "REF456",
 | ||||||
|  | // 				Count:                decimal.RequireFromString("10"),
 | ||||||
|  | // 				OrderFee:             decimal.RequireFromString("1"),
 | ||||||
|  | // 				Amount:               decimal.RequireFromString("100"),
 | ||||||
|  | // 				ReferenceBrand:       nil,
 | ||||||
|  | // 				ReferenceUID:         nil,
 | ||||||
|  | // 				WalletStatus:         nil,
 | ||||||
|  | // 				ThreePartyStatus:     nil,
 | ||||||
|  | // 				DirectionType:        nil,
 | ||||||
|  | // 				CryptoType:           nil,
 | ||||||
|  | // 				ThirdPartyFee:        nil,
 | ||||||
|  | // 				CryptoToUSDTRate:     nil,
 | ||||||
|  | // 				FiatToUSDRate:        nil,
 | ||||||
|  | // 				FeeCryptoToUSDTRate:  nil,
 | ||||||
|  | // 				USDTToCryptoTypeRate: nil,
 | ||||||
|  | // 				PaymentFiat:          nil,
 | ||||||
|  | // 				PaymentUnitPrice:     nil,
 | ||||||
|  | // 				PaymentTemplateID:    nil,
 | ||||||
|  | // 				OrderArrivalTime:     nil,
 | ||||||
|  | // 				OrderPaymentTime:     nil,
 | ||||||
|  | // 				UnpaidTimeoutSecond:  nil,
 | ||||||
|  | // 				ChainType:            nil,
 | ||||||
|  | // 				TxHash:               nil,
 | ||||||
|  | // 				FromAddress:          nil,
 | ||||||
|  | // 				ToAddress:            nil,
 | ||||||
|  | // 				ChainFee:             nil,
 | ||||||
|  | // 				ChainFeeCrypto:       nil,
 | ||||||
|  | // 				Memo:                 nil,
 | ||||||
|  | // 				OrderNote:            nil,
 | ||||||
|  | // 				CreateTime:           1620000000,
 | ||||||
|  | // 				UpdateTime:           1630000000,
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expected: &order.GetOrderResp{
 | ||||||
|  | // 				BusinessId:           "B456",
 | ||||||
|  | // 				OrderType:            1,
 | ||||||
|  | // 				OrderStatus:          2,
 | ||||||
|  | // 				Brand:                "TestBrand",
 | ||||||
|  | // 				OrderUid:             "UID456",
 | ||||||
|  | // 				ReferenceId:          "REF456",
 | ||||||
|  | // 				Count:                "10",
 | ||||||
|  | // 				OrderFee:             "1",
 | ||||||
|  | // 				Amount:               "100",
 | ||||||
|  | // 				ReferenceBrand:       nil,
 | ||||||
|  | // 				ReferenceUid:         nil,
 | ||||||
|  | // 				WalletStatus:         nil,
 | ||||||
|  | // 				ThreePartyStatus:     nil,
 | ||||||
|  | // 				DirectionType:        nil,
 | ||||||
|  | // 				CryptoType:           nil,
 | ||||||
|  | // 				ThirdPartyFee:        nil,
 | ||||||
|  | // 				CryptoToUsdtRate:     nil,
 | ||||||
|  | // 				FiatToUsdRate:        nil,
 | ||||||
|  | // 				FeeCryptoToUsdtRate:  nil,
 | ||||||
|  | // 				UsdtToCryptoTypeRate: nil,
 | ||||||
|  | // 				PaymentFiat:          nil,
 | ||||||
|  | // 				PaymentUnitPrice:     nil,
 | ||||||
|  | // 				PaymentTemplateId:    nil,
 | ||||||
|  | // 				OrderArrivalTime:     nil,
 | ||||||
|  | // 				OrderPaymentTime:     nil,
 | ||||||
|  | // 				UnpaidTimeoutSecond:  nil,
 | ||||||
|  | // 				ChainType:            nil,
 | ||||||
|  | // 				TxHash:               nil,
 | ||||||
|  | // 				FromAddress:          nil,
 | ||||||
|  | // 				ToAddress:            nil,
 | ||||||
|  | // 				ChainFee:             nil,
 | ||||||
|  | // 				ChainFeeCrypto:       nil,
 | ||||||
|  | // 				Memo:                 nil,
 | ||||||
|  | // 				OrderNote:            nil,
 | ||||||
|  | // 				CreateTime:           1620000000,
 | ||||||
|  | // 				UpdateTime:           1630000000,
 | ||||||
|  | // 			},
 | ||||||
|  | // 		},
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // 	for _, tt := range tests {
 | ||||||
|  | // 		t.Run(tt.name, func(t *testing.T) {
 | ||||||
|  | // 			// 執行 ConvertOrderToGetOrderResp 函數
 | ||||||
|  | // 			result := ConvertOrderToGetOrderResp(tt.input)
 | ||||||
|  | //
 | ||||||
|  | // 			// 驗證結果
 | ||||||
|  | // 			assert.Equal(t, tt.expected, result)
 | ||||||
|  | // 		})
 | ||||||
|  | // 	}
 | ||||||
|  | // }
 | ||||||
|  | //
 | ||||||
|  | // func TestListOrder(t *testing.T) {
 | ||||||
|  | // 	ctrl := gomock.NewController(t)
 | ||||||
|  | // 	defer ctrl.Finish()
 | ||||||
|  | //
 | ||||||
|  | // 	// 初始化 mock 依賴
 | ||||||
|  | // 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
 | ||||||
|  | // 	mockValidate := mocksvc.NewMockValidate(ctrl)
 | ||||||
|  | //
 | ||||||
|  | // 	// 初始化服務上下文
 | ||||||
|  | // 	svcCtx := &svc.ServiceContext{
 | ||||||
|  | // 		OrderModel: mockOrderModel,
 | ||||||
|  | // 		Validate:   mockValidate,
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // 	// 測試數據集
 | ||||||
|  | // 	tests := []struct {
 | ||||||
|  | // 		name        string
 | ||||||
|  | // 		input       *order.ListOrderReq
 | ||||||
|  | // 		prepare     func()
 | ||||||
|  | // 		expectErr   bool
 | ||||||
|  | // 		expectedRes *order.ListOrderResp
 | ||||||
|  | // 	}{
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "成功取得訂單列表",
 | ||||||
|  | // 			input: &order.ListOrderReq{
 | ||||||
|  | // 				PageIndex: 1,
 | ||||||
|  | // 				PageSize:  10,
 | ||||||
|  | // 			},
 | ||||||
|  | // 			prepare: func() {
 | ||||||
|  | // 				// 模擬驗證成功
 | ||||||
|  | // 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
 | ||||||
|  | // 				// 模擬返回訂單列表
 | ||||||
|  | // 				mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return([]model.Order{
 | ||||||
|  | // 					{
 | ||||||
|  | // 						BusinessID:  "B123",
 | ||||||
|  | // 						OrderType:   1,
 | ||||||
|  | // 						OrderStatus: 2,
 | ||||||
|  | // 						Brand:       "TestBrand",
 | ||||||
|  | // 						OrderUID:    "UID123",
 | ||||||
|  | // 						ReferenceID: "REF123",
 | ||||||
|  | // 						Count:       decimal.RequireFromString("10.5"),
 | ||||||
|  | // 						OrderFee:    decimal.RequireFromString("0.5"),
 | ||||||
|  | // 						Amount:      decimal.RequireFromString("100"),
 | ||||||
|  | // 					},
 | ||||||
|  | // 				}, int64(1), nil).Times(1)
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expectErr: false,
 | ||||||
|  | // 			expectedRes: &order.ListOrderResp{
 | ||||||
|  | // 				Data: []*order.GetOrderResp{
 | ||||||
|  | // 					{
 | ||||||
|  | // 						BusinessId:  "B123",
 | ||||||
|  | // 						OrderType:   1,
 | ||||||
|  | // 						OrderStatus: 2,
 | ||||||
|  | // 						Brand:       "TestBrand",
 | ||||||
|  | // 						OrderUid:    "UID123",
 | ||||||
|  | // 						ReferenceId: "REF123",
 | ||||||
|  | // 						Count:       "10.5",
 | ||||||
|  | // 						OrderFee:    "0.5",
 | ||||||
|  | // 						Amount:      "100",
 | ||||||
|  | // 					},
 | ||||||
|  | // 				},
 | ||||||
|  | // 				Page: &order.Pager{
 | ||||||
|  | // 					Total: 1,
 | ||||||
|  | // 					Index: 1,
 | ||||||
|  | // 					Size:  10,
 | ||||||
|  | // 				},
 | ||||||
|  | // 			},
 | ||||||
|  | // 		},
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "訂單查詢失敗",
 | ||||||
|  | // 			input: &order.ListOrderReq{
 | ||||||
|  | // 				PageIndex: 1,
 | ||||||
|  | // 				PageSize:  10,
 | ||||||
|  | // 			},
 | ||||||
|  | // 			prepare: func() {
 | ||||||
|  | // 				// 模擬驗證成功
 | ||||||
|  | // 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
 | ||||||
|  | // 				// 模擬返回查詢錯誤
 | ||||||
|  | // 				mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("query failed")).Times(1)
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expectErr: true,
 | ||||||
|  | // 		},
 | ||||||
|  | // 		{
 | ||||||
|  | // 			name: "分頁錯誤",
 | ||||||
|  | // 			input: &order.ListOrderReq{
 | ||||||
|  | // 				PageIndex: 0,
 | ||||||
|  | // 				PageSize:  10,
 | ||||||
|  | // 			},
 | ||||||
|  | // 			prepare: func() {
 | ||||||
|  | // 				// 模擬驗證失敗,因為 PageIndex 為 0
 | ||||||
|  | // 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid pagination")).Times(1)
 | ||||||
|  | // 			},
 | ||||||
|  | // 			expectErr: true,
 | ||||||
|  | // 		},
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // 	for _, tt := range tests {
 | ||||||
|  | // 		t.Run(tt.name, func(t *testing.T) {
 | ||||||
|  | // 			// 準備測試環境
 | ||||||
|  | // 			tt.prepare()
 | ||||||
|  | //
 | ||||||
|  | // 			// 初始化 ListOrderLogic
 | ||||||
|  | // 			logic := NewListOrderLogic(context.TODO(), svcCtx)
 | ||||||
|  | //
 | ||||||
|  | // 			// 執行 ListOrder
 | ||||||
|  | // 			resp, err := logic.ListOrder(tt.input)
 | ||||||
|  | //
 | ||||||
|  | // 			// 驗證結果
 | ||||||
|  | // 			if tt.expectErr {
 | ||||||
|  | // 				assert.Error(t, err)
 | ||||||
|  | // 				assert.Nil(t, resp)
 | ||||||
|  | // 			} else {
 | ||||||
|  | // 				assert.NoError(t, err)
 | ||||||
|  | // 				assert.Equal(t, tt.expectedRes, resp)
 | ||||||
|  | // 			}
 | ||||||
|  | // 		})
 | ||||||
|  | // 	}
 | ||||||
|  | // }
 | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| package orderservicelogic |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 
 |  | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" |  | ||||||
| 
 |  | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type ModifyOrderLogic struct { |  | ||||||
| 	ctx    context.Context |  | ||||||
| 	svcCtx *svc.ServiceContext |  | ||||||
| 	logx.Logger |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewModifyOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyOrderLogic { |  | ||||||
| 	return &ModifyOrderLogic{ |  | ||||||
| 		ctx:    ctx, |  | ||||||
| 		svcCtx: svcCtx, |  | ||||||
| 		Logger: logx.WithContext(ctx), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ModifyOrder 修改訂單
 |  | ||||||
| func (l *ModifyOrderLogic) ModifyOrder(in *tweeting.ModifyOrderReq) (*tweeting.OKResp, error) { |  | ||||||
| 	// todo: add your logic here and delete this line
 |  | ||||||
| 
 |  | ||||||
| 	return &tweeting.OKResp{}, nil |  | ||||||
| } |  | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	"app-cloudep-order-server/internal/domain" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
|  | 	ers "code.30cm.net/digimon/library-go/errs" | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | @ -23,9 +26,46 @@ func NewModifyOrderStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ModifyOrderStatus 修改訂單狀態
 | // ModifyOrderQuery
 | ||||||
| func (l *ModifyOrderStatusLogic) ModifyOrderStatus(in *tweeting.ModifyOrderStatusReq) (*tweeting.OKResp, error) { | // 0.建立訂單 1.建單失敗 2.審核中 3.付款中 4.已付款
 | ||||||
| 	// todo: add your logic here and delete this line
 | // 5.已付款待轉帳 6.申訴中 7.交易完成
 | ||||||
|  | // 8.交易失敗 9.交易取消 10.交易異常 11.交易超時
 | ||||||
| 
 | 
 | ||||||
| 	return &tweeting.OKResp{}, nil | 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 *order.ModifyOrderStatusReq) (*order.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.OrderModel.UpdateStatus(l.ctx, | ||||||
|  | 		model.UpdateStatusReq{ | ||||||
|  | 			BusinessID: in.GetBusinessId(), | ||||||
|  | 			Status:     in.GetStatus(), | ||||||
|  | 		}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// 錯誤代碼 06-021-02
 | ||||||
|  | 		e := domain.CommentErrorL( | ||||||
|  | 			domain.ModifyOrderErrorCode, | ||||||
|  | 			logx.WithContext(l.ctx), | ||||||
|  | 			[]logx.LogField{ | ||||||
|  | 				{Key: "req", Value: in}, | ||||||
|  | 				{Key: "func", Value: "OrderModel.UpdateStatus"}, | ||||||
|  | 				{Key: "err", Value: err}, | ||||||
|  | 			}, | ||||||
|  | 			"failed to update order status:").Wrap(err) | ||||||
|  | 
 | ||||||
|  | 		return nil, e | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.OKResp{}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mocksvc "app-cloudep-order-server/internal/mock/lib" | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestModifyOrderStatus(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 	mockValidate := mocksvc.NewMockValidate(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 		Validate:   mockValidate, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		input       *order.ModifyOrderStatusReq | ||||||
|  | 		prepare     func() | ||||||
|  | 		expectErr   bool | ||||||
|  | 		expectedRes *order.OKResp | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "成功修改訂單狀態", | ||||||
|  | 			input: &order.ModifyOrderStatusReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 				Status:     3, | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬狀態更新成功
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr:   false, | ||||||
|  | 			expectedRes: &order.OKResp{}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "驗證失敗", | ||||||
|  | 			input: &order.ModifyOrderStatusReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 				Status:     12, // 錯誤的狀態值
 | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證失敗
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid status")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "修改訂單狀態失敗", | ||||||
|  | 			input: &order.ModifyOrderStatusReq{ | ||||||
|  | 				BusinessId: "B123", | ||||||
|  | 				Status:     3, | ||||||
|  | 			}, | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬驗證成功
 | ||||||
|  | 				mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1) | ||||||
|  | 				// 模擬狀態更新失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), gomock.Any()).Return(nil, errors.New("update failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 準備測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 ModifyOrderStatusLogic
 | ||||||
|  | 			logic := NewModifyOrderStatusLogic(context.TODO(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 			// 執行 ModifyOrderStatus
 | ||||||
|  | 			resp, err := logic.ModifyOrderStatus(tt.input) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Equal(t, tt.expectedRes, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
| package orderservicelogic | package orderservicelogic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	"app-cloudep-order-server/internal/domain" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | @ -24,8 +27,25 @@ func NewOrderStatusTimeoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
 | // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
 | ||||||
| func (l *OrderStatusTimeoutLogic) OrderStatusTimeout(in *tweeting.OrderStatusTimeoutReq) (*tweeting.OKResp, error) { | func (l *OrderStatusTimeoutLogic) OrderStatusTimeout(_ *order.OrderStatusTimeoutReq) (*order.OKResp, error) { | ||||||
| 	// todo: add your logic here and delete this line
 | 	now := time.Now().UTC().UnixNano() | ||||||
|  | 	_, err := l.svcCtx.OrderModel.UpdateTimeoutOrder(l.ctx, model.UpdateTimeoutReq{ | ||||||
|  | 		CreateTimeBefore: now, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// 錯誤代碼 06-021-02
 | ||||||
|  | 		e := domain.CommentErrorL( | ||||||
|  | 			domain.TimeoutOrderErrorCode, | ||||||
|  | 			logx.WithContext(l.ctx), | ||||||
|  | 			[]logx.LogField{ | ||||||
|  | 				{Key: "now", Value: now}, | ||||||
|  | 				{Key: "func", Value: "OrderModel.UpdateTimeoutOrder"}, | ||||||
|  | 				{Key: "err", Value: err}, | ||||||
|  | 			}, | ||||||
|  | 			"failed to update timeout order").Wrap(err) | ||||||
| 
 | 
 | ||||||
| 	return &tweeting.OKResp{}, nil | 		return nil, e | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &order.OKResp{}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | package orderservicelogic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
|  | 	mockmodel "app-cloudep-order-server/internal/mock/model" | ||||||
|  | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"go.uber.org/mock/gomock" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestOrderStatusTimeout(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 
 | ||||||
|  | 	// 初始化 mock 依賴
 | ||||||
|  | 	mockOrderModel := mockmodel.NewMockOrderModel(ctrl) | ||||||
|  | 
 | ||||||
|  | 	// 初始化服務上下文
 | ||||||
|  | 	svcCtx := &svc.ServiceContext{ | ||||||
|  | 		OrderModel: mockOrderModel, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 測試數據集
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		prepare     func() | ||||||
|  | 		expectErr   bool | ||||||
|  | 		expectedRes *order.OKResp | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "成功更新訂單狀態", | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬訂單狀態超時更新成功
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr:   false, | ||||||
|  | 			expectedRes: &order.OKResp{}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "更新訂單狀態失敗", | ||||||
|  | 			prepare: func() { | ||||||
|  | 				// 模擬訂單狀態超時更新失敗
 | ||||||
|  | 				mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, errors.New("update failed")).Times(1) | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			// 準備測試環境
 | ||||||
|  | 			tt.prepare() | ||||||
|  | 
 | ||||||
|  | 			// 初始化 OrderStatusTimeoutLogic
 | ||||||
|  | 			logic := NewOrderStatusTimeoutLogic(context.TODO(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 			// 執行 OrderStatusTimeout
 | ||||||
|  | 			resp, err := logic.OrderStatusTimeout(&order.OrderStatusTimeoutReq{}) | ||||||
|  | 
 | ||||||
|  | 			// 驗證結果
 | ||||||
|  | 			if tt.expectErr { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				assert.Nil(t, resp) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Equal(t, tt.expectedRes, resp) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -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) | ||||||
|  | } | ||||||
|  | @ -10,11 +10,11 @@ | ||||||
| package mock | package mock | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	mongo "app-cloudep-order-server/internal/model/mongo" | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	context "context" | 	context "context" | ||||||
| 	reflect "reflect" | 	reflect "reflect" | ||||||
| 
 | 
 | ||||||
| 	mongo0 "go.mongodb.org/mongo-driver/mongo" | 	mongo "go.mongodb.org/mongo-driver/mongo" | ||||||
| 	gomock "go.uber.org/mock/gomock" | 	gomock "go.uber.org/mock/gomock" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -56,11 +56,26 @@ func (mr *MockOrderModelMockRecorder) Delete(ctx, id any) *gomock.Call { | ||||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockOrderModel)(nil).Delete), ctx, id) | 	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) (*mongo.UpdateResult, error) { | ||||||
|  | 	m.ctrl.T.Helper() | ||||||
|  | 	ret := m.ctrl.Call(m, "DeleteByBusinessID", ctx, id) | ||||||
|  | 	ret0, _ := ret[0].(*mongo.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.
 | // FindOne mocks base method.
 | ||||||
| func (m *MockOrderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) { | func (m *MockOrderModel) FindOne(ctx context.Context, id string) (*model.Order, error) { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "FindOne", ctx, id) | 	ret := m.ctrl.Call(m, "FindOne", ctx, id) | ||||||
| 	ret0, _ := ret[0].(*mongo.Order) | 	ret0, _ := ret[0].(*model.Order) | ||||||
| 	ret1, _ := ret[1].(error) | 	ret1, _ := ret[1].(error) | ||||||
| 	return ret0, ret1 | 	return ret0, ret1 | ||||||
| } | } | ||||||
|  | @ -71,8 +86,23 @@ func (mr *MockOrderModelMockRecorder) FindOne(ctx, id any) *gomock.Call { | ||||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockOrderModel)(nil).FindOne), ctx, id) | 	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) (*model.Order, error) { | ||||||
|  | 	m.ctrl.T.Helper() | ||||||
|  | 	ret := m.ctrl.Call(m, "FindOneBusinessID", ctx, id) | ||||||
|  | 	ret0, _ := ret[0].(*model.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.
 | // Insert mocks base method.
 | ||||||
| func (m *MockOrderModel) Insert(ctx context.Context, data *mongo.Order) error { | func (m *MockOrderModel) Insert(ctx context.Context, data *model.Order) error { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "Insert", ctx, data) | 	ret := m.ctrl.Call(m, "Insert", ctx, data) | ||||||
| 	ret0, _ := ret[0].(error) | 	ret0, _ := ret[0].(error) | ||||||
|  | @ -85,11 +115,27 @@ func (mr *MockOrderModelMockRecorder) Insert(ctx, data any) *gomock.Call { | ||||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockOrderModel)(nil).Insert), ctx, data) | 	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 model.GetOrderListReq) ([]model.Order, int64, error) { | ||||||
|  | 	m.ctrl.T.Helper() | ||||||
|  | 	ret := m.ctrl.Call(m, "ListOrder", ctx, req) | ||||||
|  | 	ret0, _ := ret[0].([]model.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.
 | // Update mocks base method.
 | ||||||
| func (m *MockOrderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) { | func (m *MockOrderModel) Update(ctx context.Context, data *model.Order) (*mongo.UpdateResult, error) { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "Update", ctx, data) | 	ret := m.ctrl.Call(m, "Update", ctx, data) | ||||||
| 	ret0, _ := ret[0].(*mongo0.UpdateResult) | 	ret0, _ := ret[0].(*mongo.UpdateResult) | ||||||
| 	ret1, _ := ret[1].(error) | 	ret1, _ := ret[1].(error) | ||||||
| 	return ret0, ret1 | 	return ret0, ret1 | ||||||
| } | } | ||||||
|  | @ -99,3 +145,33 @@ func (mr *MockOrderModelMockRecorder) Update(ctx, data any) *gomock.Call { | ||||||
| 	mr.mock.ctrl.T.Helper() | 	mr.mock.ctrl.T.Helper() | ||||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrderModel)(nil).Update), ctx, data) | 	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 model.UpdateStatusReq) (*mongo.UpdateResult, error) { | ||||||
|  | 	m.ctrl.T.Helper() | ||||||
|  | 	ret := m.ctrl.Call(m, "UpdateStatus", ctx, data) | ||||||
|  | 	ret0, _ := ret[0].(*mongo.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 model.UpdateTimeoutReq) (*mongo.UpdateResult, error) { | ||||||
|  | 	m.ctrl.T.Helper() | ||||||
|  | 	ret := m.ctrl.Call(m, "UpdateTimeoutOrder", ctx, req) | ||||||
|  | 	ret0, _ := ret[0].(*mongo.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) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -10,11 +10,11 @@ | ||||||
| package mock | package mock | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	mongo "app-cloudep-order-server/internal/model/mongo" | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
| 	context "context" | 	context "context" | ||||||
| 	reflect "reflect" | 	reflect "reflect" | ||||||
| 
 | 
 | ||||||
| 	mongo0 "go.mongodb.org/mongo-driver/mongo" | 	mongo "go.mongodb.org/mongo-driver/mongo" | ||||||
| 	gomock "go.uber.org/mock/gomock" | 	gomock "go.uber.org/mock/gomock" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -57,10 +57,10 @@ func (mr *MockorderModelMockRecorder) Delete(ctx, id any) *gomock.Call { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FindOne mocks base method.
 | // FindOne mocks base method.
 | ||||||
| func (m *MockorderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) { | func (m *MockorderModel) FindOne(ctx context.Context, id string) (*model.Order, error) { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "FindOne", ctx, id) | 	ret := m.ctrl.Call(m, "FindOne", ctx, id) | ||||||
| 	ret0, _ := ret[0].(*mongo.Order) | 	ret0, _ := ret[0].(*model.Order) | ||||||
| 	ret1, _ := ret[1].(error) | 	ret1, _ := ret[1].(error) | ||||||
| 	return ret0, ret1 | 	return ret0, ret1 | ||||||
| } | } | ||||||
|  | @ -72,7 +72,7 @@ func (mr *MockorderModelMockRecorder) FindOne(ctx, id any) *gomock.Call { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Insert mocks base method.
 | // Insert mocks base method.
 | ||||||
| func (m *MockorderModel) Insert(ctx context.Context, data *mongo.Order) error { | func (m *MockorderModel) Insert(ctx context.Context, data *model.Order) error { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "Insert", ctx, data) | 	ret := m.ctrl.Call(m, "Insert", ctx, data) | ||||||
| 	ret0, _ := ret[0].(error) | 	ret0, _ := ret[0].(error) | ||||||
|  | @ -86,10 +86,10 @@ func (mr *MockorderModelMockRecorder) Insert(ctx, data any) *gomock.Call { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Update mocks base method.
 | // Update mocks base method.
 | ||||||
| func (m *MockorderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) { | func (m *MockorderModel) Update(ctx context.Context, data *model.Order) (*mongo.UpdateResult, error) { | ||||||
| 	m.ctrl.T.Helper() | 	m.ctrl.T.Helper() | ||||||
| 	ret := m.ctrl.Call(m, "Update", ctx, data) | 	ret := m.ctrl.Call(m, "Update", ctx, data) | ||||||
| 	ret0, _ := ret[0].(*mongo0.UpdateResult) | 	ret0, _ := ret[0].(*mongo.UpdateResult) | ||||||
| 	ret1, _ := ret[1].(error) | 	ret1, _ := ret[1].(error) | ||||||
| 	return ret0, ret1 | 	return ret0, ret1 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,17 @@ | ||||||
| package model | package model | ||||||
| 
 | 
 | ||||||
| import "github.com/zeromicro/go-zero/core/stores/mon" | import ( | ||||||
|  | 	"app-cloudep-order-server/internal/domain" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
|  | 
 | ||||||
|  | 	"github.com/zeromicro/go-zero/core/stores/mon" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| var _ OrderModel = (*customOrderModel)(nil) | var _ OrderModel = (*customOrderModel)(nil) | ||||||
| 
 | 
 | ||||||
|  | @ -9,11 +20,50 @@ type ( | ||||||
| 	// and implement the added methods in customOrderModel.
 | 	// and implement the added methods in customOrderModel.
 | ||||||
| 	OrderModel interface { | 	OrderModel interface { | ||||||
| 		orderModel | 		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 { | 	customOrderModel struct { | ||||||
| 		*defaultOrderModel | 		*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 []int32 `json:"direction_type"` | ||||||
|  | 		OrderStatus   []int32 `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.
 | // NewOrderModel returns a model for the mongo.
 | ||||||
|  | @ -23,3 +73,168 @@ func NewOrderModel(url, db, collection string) OrderModel { | ||||||
| 		defaultOrderModel: newDefaultOrderModel(conn), | 		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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -29,8 +29,8 @@ func newDefaultOrderModel(conn *mon.Model) *defaultOrderModel { | ||||||
| func (m *defaultOrderModel) Insert(ctx context.Context, data *Order) error { | func (m *defaultOrderModel) Insert(ctx context.Context, data *Order) error { | ||||||
| 	if data.ID.IsZero() { | 	if data.ID.IsZero() { | ||||||
| 		data.ID = primitive.NewObjectID() | 		data.ID = primitive.NewObjectID() | ||||||
| 		data.CreateAt = time.Now() | 		data.CreateTime = time.Now().UTC().UnixNano() | ||||||
| 		data.UpdateAt = time.Now() | 		data.UpdateTime = time.Now().UTC().UnixNano() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err := m.conn.InsertOne(ctx, data) | 	_, err := m.conn.InsertOne(ctx, data) | ||||||
|  | @ -57,7 +57,7 @@ func (m *defaultOrderModel) FindOne(ctx context.Context, id string) (*Order, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *defaultOrderModel) Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) { | func (m *defaultOrderModel) Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) { | ||||||
| 	data.UpdateAt = time.Now() | 	data.UpdateTime = time.Now().UTC().UnixNano() | ||||||
| 
 | 
 | ||||||
| 	res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data}) | 	res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data}) | ||||||
| 	return res, err | 	return res, err | ||||||
|  |  | ||||||
|  | @ -1,14 +1,49 @@ | ||||||
| package model | package model | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"go.mongodb.org/mongo-driver/bson/primitive" | 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Order struct { | type Order struct { | ||||||
| 	ID                   primitive.ObjectID    `bson:"_id,omitempty" json:"id,omitempty"` | 	ID                   primitive.ObjectID    `bson:"_id,omitempty" json:"id,omitempty"` | ||||||
| 	// TODO: Fill your own fields
 | 	UpdateTime           int64                 `bson:"update_time"` | ||||||
| 	UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` | 	CreateTime           int64                 `bson:"create_time"` | ||||||
| 	CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` | 	BusinessID           string                `bson:"business_id"`                        // 訂單業務流水號
 | ||||||
|  | 	OrderType            int8                  `bson:"order_type"`                         // 訂單類型
 | ||||||
|  | 	OrderStatus          int8                  `bson:"order_status"`                       // 訂單狀態
 | ||||||
|  | 	Brand                string                `bson:"brand"`                              // 下單平台
 | ||||||
|  | 	OrderUID             string                `bson:"order_uid"`                          // 下單用戶 UID
 | ||||||
|  | 	ReferenceID          string                `bson:"reference_id"`                       // 訂單來源
 | ||||||
|  | 	Count                primitive.Decimal128  `bson:"count"`                              // 訂單數量
 | ||||||
|  | 	OrderFee             primitive.Decimal128  `bson:"order_fee"`                          // 訂單手續費
 | ||||||
|  | 	Amount               primitive.Decimal128  `bson:"amount"`                             // 單價
 | ||||||
|  | 	ReferenceBrand       *string               `bson:"reference_brand,omitempty"`          // 訂單來源平台
 | ||||||
|  | 	ReferenceUID         *string               `bson:"reference_uid,omitempty"`            // 訂單來源用戶 UID
 | ||||||
|  | 	WalletStatus         *int64                `bson:"wallet_status,omitempty"`            // 交易金額狀態
 | ||||||
|  | 	ThreePartyStatus     *int64                `bson:"three_party_status,omitempty"`       // 三方請求狀態
 | ||||||
|  | 	DirectionType        *int64                `bson:"direction_type,omitempty"`           // 交易方向
 | ||||||
|  | 	CryptoType           *string               `bson:"crypto_type,omitempty"`              // 交易幣種
 | ||||||
|  | 	ThirdPartyFee        *primitive.Decimal128 `bson:"third_party_fee,omitempty"`          // 第三方手續費
 | ||||||
|  | 	CryptoToUSDTRate     *primitive.Decimal128 `bson:"crypto_to_usdt_rate,omitempty"`      // 加密貨幣對 USDT 匯率
 | ||||||
|  | 	FiatToUSDRate        *primitive.Decimal128 `bson:"fiat_to_usd_rate,omitempty"`         // 法幣對 USD 匯率
 | ||||||
|  | 	FeeCryptoToUSDTRate  *primitive.Decimal128 `bson:"fee_crypto_to_usdt_rate,omitempty"`  // 手續費加密貨幣對 USDT 匯率
 | ||||||
|  | 	USDTToCryptoTypeRate *primitive.Decimal128 `bson:"usdt_to_crypto_type_rate,omitempty"` // USDT 對加密貨幣匯率
 | ||||||
|  | 	PaymentFiat          *string               `bson:"payment_fiat,omitempty"`             // 支付法幣
 | ||||||
|  | 	PaymentUnitPrice     *primitive.Decimal128 `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             *primitive.Decimal128 `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" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,14 +6,14 @@ package server | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
| 	"app-cloudep-order-server/internal/logic/orderservice" | 	orderservicelogic "app-cloudep-order-server/internal/logic/orderservice" | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type OrderServiceServer struct { | type OrderServiceServer struct { | ||||||
| 	svcCtx *svc.ServiceContext | 	svcCtx *svc.ServiceContext | ||||||
| 	tweeting.UnimplementedOrderServiceServer | 	order.UnimplementedOrderServiceServer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer { | func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer { | ||||||
|  | @ -23,49 +23,43 @@ func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateOrder 建立訂單
 | // CreateOrder 建立訂單
 | ||||||
| func (s *OrderServiceServer) CreateOrder(ctx context.Context, in *tweeting.CreateOrderReq) (*tweeting.CreateOrderResp, error) { | func (s *OrderServiceServer) CreateOrder(ctx context.Context, in *order.CreateOrderReq) (*order.OKResp, error) { | ||||||
| 	l := orderservicelogic.NewCreateOrderLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewCreateOrderLogic(ctx, s.svcCtx) | ||||||
| 	return l.CreateOrder(in) | 	return l.CreateOrder(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CancelOrder 取消訂單
 | // CancelOrder 取消訂單
 | ||||||
| func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *tweeting.CancelOrderReq) (*tweeting.OKResp, error) { | func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *order.CancelOrderReq) (*order.OKResp, error) { | ||||||
| 	l := orderservicelogic.NewCancelOrderLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewCancelOrderLogic(ctx, s.svcCtx) | ||||||
| 	return l.CancelOrder(in) | 	return l.CancelOrder(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ModifyOrder 修改訂單
 |  | ||||||
| func (s *OrderServiceServer) ModifyOrder(ctx context.Context, in *tweeting.ModifyOrderReq) (*tweeting.OKResp, error) { |  | ||||||
| 	l := orderservicelogic.NewModifyOrderLogic(ctx, s.svcCtx) |  | ||||||
| 	return l.ModifyOrder(in) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ModifyOrderStatus 修改訂單狀態
 | // ModifyOrderStatus 修改訂單狀態
 | ||||||
| func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *tweeting.ModifyOrderStatusReq) (*tweeting.OKResp, error) { | func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *order.ModifyOrderStatusReq) (*order.OKResp, error) { | ||||||
| 	l := orderservicelogic.NewModifyOrderStatusLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewModifyOrderStatusLogic(ctx, s.svcCtx) | ||||||
| 	return l.ModifyOrderStatus(in) | 	return l.ModifyOrderStatus(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeleteOrder 刪除訂單(軟刪除)
 | // DeleteOrder 刪除訂單(軟刪除)
 | ||||||
| func (s *OrderServiceServer) DeleteOrder(ctx context.Context, in *tweeting.DeleteOrderReq) (*tweeting.OKResp, error) { | func (s *OrderServiceServer) DeleteOrder(ctx context.Context, in *order.DeleteOrderReq) (*order.OKResp, error) { | ||||||
| 	l := orderservicelogic.NewDeleteOrderLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewDeleteOrderLogic(ctx, s.svcCtx) | ||||||
| 	return l.DeleteOrder(in) | 	return l.DeleteOrder(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetOrder 取得訂單詳情
 | // GetOrder 取得訂單詳情
 | ||||||
| func (s *OrderServiceServer) GetOrder(ctx context.Context, in *tweeting.GetOrderReq) (*tweeting.GetOrderResp, error) { | func (s *OrderServiceServer) GetOrder(ctx context.Context, in *order.GetOrderReq) (*order.GetOrderResp, error) { | ||||||
| 	l := orderservicelogic.NewGetOrderLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewGetOrderLogic(ctx, s.svcCtx) | ||||||
| 	return l.GetOrder(in) | 	return l.GetOrder(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ListOrder 取得訂單列表
 | // ListOrder 取得訂單列表
 | ||||||
| func (s *OrderServiceServer) ListOrder(ctx context.Context, in *tweeting.ListOrderReq) (*tweeting.ListOrderResp, error) { | func (s *OrderServiceServer) ListOrder(ctx context.Context, in *order.ListOrderReq) (*order.ListOrderResp, error) { | ||||||
| 	l := orderservicelogic.NewListOrderLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewListOrderLogic(ctx, s.svcCtx) | ||||||
| 	return l.ListOrder(in) | 	return l.ListOrder(in) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
 | // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
 | ||||||
| func (s *OrderServiceServer) OrderStatusTimeout(ctx context.Context, in *tweeting.OrderStatusTimeoutReq) (*tweeting.OKResp, error) { | func (s *OrderServiceServer) OrderStatusTimeout(ctx context.Context, in *order.OrderStatusTimeoutReq) (*order.OKResp, error) { | ||||||
| 	l := orderservicelogic.NewOrderStatusTimeoutLogic(ctx, s.svcCtx) | 	l := orderservicelogic.NewOrderStatusTimeoutLogic(ctx, s.svcCtx) | ||||||
| 	return l.OrderStatusTimeout(in) | 	return l.OrderStatusTimeout(in) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | package svc | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"app-cloudep-order-server/internal/config" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func mustMongoConnectURL(c config.Config) string { | ||||||
|  | 	return fmt.Sprintf("%s://%s:%s", | ||||||
|  | 		c.Mongo.Schema, | ||||||
|  | 		c.Mongo.Host, | ||||||
|  | 		c.Mongo.Port, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO 思考快取做在那邊
 | ||||||
|  | 
 | ||||||
|  | func MustOrderModel(c config.Config) model.OrderModel { | ||||||
|  | 	orderCollection := model.Order{} | ||||||
|  | 
 | ||||||
|  | 	return model.NewOrderModel(mustMongoConnectURL(c), c.Mongo.Database, orderCollection.CollectionName()) | ||||||
|  | } | ||||||
|  | @ -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 | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,13 +1,27 @@ | ||||||
| package svc | package svc | ||||||
| 
 | 
 | ||||||
| import "app-cloudep-order-server/internal/config" | import ( | ||||||
|  | 	"app-cloudep-order-server/internal/config" | ||||||
|  | 	model "app-cloudep-order-server/internal/model/mongo" | ||||||
|  | 
 | ||||||
|  | 	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 { | type ServiceContext struct { | ||||||
| 	Config   config.Config | 	Config   config.Config | ||||||
|  | 	Validate vi.Validate | ||||||
|  | 
 | ||||||
|  | 	OrderModel model.OrderModel | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewServiceContext(c config.Config) *ServiceContext { | func NewServiceContext(c config.Config) *ServiceContext { | ||||||
|  | 	ers.Scope = code.CloudEPOrder | ||||||
|  | 
 | ||||||
| 	return &ServiceContext{ | 	return &ServiceContext{ | ||||||
| 		Config:     c, | 		Config:     c, | ||||||
|  | 		Validate:   vi.MustValidator(WithDecimalGt(), WithDecimalGte()), | ||||||
|  | 		OrderModel: MustOrderModel(c), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								order.go
								
								
								
								
							
							
						
						
									
										5
									
								
								order.go
								
								
								
								
							|  | @ -1,10 +1,10 @@ | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"app-cloudep-order-server/gen_result/pb/order" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
| 	"app-cloudep-order-server/gen_result/pb/tweeting" |  | ||||||
| 	"app-cloudep-order-server/internal/config" | 	"app-cloudep-order-server/internal/config" | ||||||
| 	orderserviceServer "app-cloudep-order-server/internal/server/orderservice" | 	orderserviceServer "app-cloudep-order-server/internal/server/orderservice" | ||||||
| 	"app-cloudep-order-server/internal/svc" | 	"app-cloudep-order-server/internal/svc" | ||||||
|  | @ -26,7 +26,7 @@ func main() { | ||||||
| 	ctx := svc.NewServiceContext(c) | 	ctx := svc.NewServiceContext(c) | ||||||
| 
 | 
 | ||||||
| 	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { | 	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { | ||||||
| 		tweeting.RegisterOrderServiceServer(grpcServer, orderserviceServer.NewOrderServiceServer(ctx)) | 		order.RegisterOrderServiceServer(grpcServer, orderserviceServer.NewOrderServiceServer(ctx)) | ||||||
| 
 | 
 | ||||||
| 		if c.Mode == service.DevMode || c.Mode == service.TestMode { | 		if c.Mode == service.DevMode || c.Mode == service.TestMode { | ||||||
| 			reflection.Register(grpcServer) | 			reflection.Register(grpcServer) | ||||||
|  | @ -34,6 +34,7 @@ func main() { | ||||||
| 	}) | 	}) | ||||||
| 	defer s.Stop() | 	defer s.Stop() | ||||||
| 
 | 
 | ||||||
|  | 	//nolint:forbidigo
 | ||||||
| 	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) | 	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) | ||||||
| 	s.Start() | 	s.Start() | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue