package controller import ( "bytes" "crawler/config" "crawler/model" "crawler/utility" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "github.com/gin-gonic/gin" "io" "log" "net/http" "reflect" "strconv" "strings" "time" ) type G2AInterface interface { Init() Products(c *gin.Context) Order(c *gin.Context) } type G2A struct { ApiURL string IsTest bool Credentials config.G2ACredential G2AProductModel model.G2AProductModel G2AReportModel model.G2AReportModel G2AOrderModel model.G2AOrderModel G2AErrorModel model.G2AErrorModel G2AErrorResponse OrderDetail model.OrderDetailModel ShopConfig model.ShopConfigModel } // G2A 오류 type G2AErrorResponse struct { Status any Message any Code any } // G2A 환경설정 조회 func (this *G2A) Init() { this.IsTest = this.ShopConfig.G2AIsTest() if this.IsTest == true { this.Credentials = config.G2A.Sandbox } else { switch config.Env.DeveloperEnv { case config.LOCAL: this.Credentials = config.G2A.Sandbox case config.DEV: this.Credentials = config.G2A.Export default: log.Panic("G2A 설정이 잘못되었습니다.") } } var checksum = sha256.Sum256([]byte(fmt.Sprintf("%s%s%s", this.Credentials.Client.ID, this.Credentials.Email, this.Credentials.Client.Secret, ))) this.Credentials.Hash = fmt.Sprintf("%x", checksum[:]) } // G2A 상품 조회 func (this *G2A) Products(c *gin.Context) { this.Init() fmt.Println("G2A 상품 수집을 시작합니다.") var ( result = this.G2AProductModel.G2AProductResult total, insert, update, error, page = 0, 0, 0, 0, 1 startTime = time.Now() endTime float64 output = func(msg string) string { s := fmt.Sprintf("Total : %d\n", total) s += fmt.Sprintf("Error: %d\n", error) s += fmt.Sprintf("Insert: %d\n", insert) s += fmt.Sprintf("Update: %d\n", update) s += fmt.Sprintf("Page: %d\n", page) s += fmt.Sprintf("소요시간: %f초\n", endTime) return msg + "\n" + s } ) for { this.ApiURL = fmt.Sprintf("%s/products?page=%d", this.Credentials.API, page) receivedDTO := this.HttpGetRequest() byteData, _ := json.Marshal(receivedDTO) err := json.Unmarshal(byteData, &result) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 상품 수집 if len(result.Docs) > 0 { for i, product := range result.Docs { if this.G2AProductModel.IsExists(product.ID) { update++ } else { insert++ } if this.IsTest { result.Docs[i].IsTest = 1 } else { result.Docs[i].IsTest = 0 } } err = this.G2AProductModel.Insert(result.Docs) if err != nil { error++ utility.SendMessageToG2AError(output("[G2A 상품수집 중 오류]")) utility.Check(err, config.ERROR_LOG_PATH_G2A) break } total += len(result.Docs) page++ fmt.Printf("G2A 상품 수집 중... / total: %d, page: %d\n", total, page) } else { endTime = time.Since(startTime).Seconds() fmt.Printf("G2A 상품 수집 종료! / total: %d, page: %d\n", total, page) break } } // 통계 저장 this.G2AReportModel.Insert(model.G2AReport{ TotalCnt: total, InsertCnt: insert, UpdateCnt: update, ProcessAt: endTime, LastPage: page, }) // 텔레그램 알림 utility.SendMessageToG2AProduct(output("[G2A 상품수집 완료]")) c.JSON(http.StatusOK, gin.H{ "total": total, "insert": insert, "update": update, "page": page, }) } // G2A 품절 확인 후 상품 정보 갱신 func (this *G2A) CheckOutOfStock(c *gin.Context) { this.Init() fmt.Println("G2A 상품 품절 확인") var ( result = this.G2AProductModel.G2AProductResult productID = c.Query("id") inStock = "yes" ) if productID == "" { c.JSON(http.StatusOK, gin.H{ "inStock": "no", }) return } this.ApiURL = fmt.Sprintf("%s/products?id=%s", this.Credentials.API, productID) receivedDTO := this.HttpGetRequest() byteData, _ := json.Marshal(receivedDTO) err := json.Unmarshal(byteData, &result) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 상품 정보 확인 if len(result.Docs) > 0 { for _, product := range result.Docs { // 조회된 상품 정보를 한번 갱신한다. err = this.G2AProductModel.Update(product) utility.Check(err, config.ERROR_LOG_PATH_G2A) if product.Qty <= 0 { // 재고가 0 이하면 품절이다. inStock = "no" break } } } c.JSON(http.StatusOK, gin.H{ "inStock": inStock, }) } // G2A 주문 // Add an Order -> Pay for an order func (this *G2A) Order(c *gin.Context) { var requestKey = c.Query("requestKey") if requestKey == "" { c.JSON(http.StatusForbidden, G2AErrorResponse{ Code: http.StatusForbidden, Message: "잘못된 요청입니다.", }) return } // base64_decode 처리 rawRequestKey, err := base64.URLEncoding.DecodeString(requestKey) utility.Check(err, config.ERROR_LOG_PATH_G2A) requestKey = string(rawRequestKey) var ( key = utility.MakeMD5(config.ENCRYPT_KEY) iv = config.ENCRYPT_IV ) // 복호화 decryptCode, err := utility.AesDecrypt(requestKey, key, iv) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 이미 주문한 주문건이 있는지 확인 if this.G2AOrderModel.IsExists(decryptCode) { c.JSON(http.StatusOK, gin.H{ "result": "OK", }) return } id := strings.Split(decryptCode, "/") var ( orderID, _ = strconv.Atoi(id[0]) orderDetailID, _ = strconv.Atoi(id[1]) productID = id[2] ) if this.OrderDetail.IsExists(orderID, orderDetailID) == false { c.JSON(http.StatusNotFound, G2AErrorResponse{ Code: http.StatusNotFound, Message: "비 정상적인 주문서 입니다.", }) return } //orderDetail, err := this.OrderDetail.Info(orderID, orderDetailID) //utility.Check(err, config.ERROR_LOG_PATH_G2A) tmpProductID, _ := strconv.Atoi(productID) G2AProductInfo, err := this.G2AProductModel.Info(tmpProductID) utility.Check(err, config.ERROR_LOG_PATH_G2A) var params = this.G2AOrderModel.G2AOrderParams params.ProductID = productID params.Currency = "KRW" params.MaxPrice = G2AProductInfo.RetailMinPrice if err := c.ShouldBind(¶ms); err != nil { c.JSON(http.StatusBadRequest, G2AErrorResponse{ Code: http.StatusBadRequest, Message: err.Error(), }) return } this.Init() // 주문을 요청한다. this.ApiURL = fmt.Sprintf("%s/order", this.Credentials.API) var ( orderResult = this.G2AOrderModel.G2AOrderResult receivedDTO = this.HttpPostRequest(params) typeName = reflect.TypeOf(receivedDTO).Name() ) if typeName == "G2AErrorModel" { c.JSON(http.StatusBadRequest, receivedDTO) return } byteData, _ := json.Marshal(receivedDTO) err = json.Unmarshal(byteData, &orderResult) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 주문 번호를 결제 처리한다. this.ApiURL = fmt.Sprintf("%s/order/pay/%s", this.Credentials.API, orderResult.OrderID) var orderPayResult = this.G2AOrderModel.G2AOrderPayResult receivedDTO = this.HttpPutRequest() typeName = reflect.TypeOf(receivedDTO).Name() if typeName == "G2AErrorModel" { c.JSON(http.StatusBadRequest, receivedDTO) return } byteData, _ = json.Marshal(receivedDTO) err = json.Unmarshal(byteData, &orderPayResult) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 결제 확인 if orderPayResult.Status == false { c.JSON(http.StatusOK, gin.H{ "message": "결제에 실패하였습니다.", }) return } // 결제 및 주문이 제대로 되었는지 확인한다. this.ApiURL = fmt.Sprintf("%s/order/details/%s", this.Credentials.API, orderResult.OrderID) var orderDetailResult = this.G2AOrderModel.G2AOrderDetailResult receivedDTO = this.HttpGetRequest() typeName = reflect.TypeOf(receivedDTO).Name() if typeName == "G2AErrorModel" { c.JSON(http.StatusBadRequest, receivedDTO) return } byteData, _ = json.Marshal(receivedDTO) err = json.Unmarshal(byteData, &orderDetailResult) utility.Check(err, config.ERROR_LOG_PATH_G2A) time.Sleep(1 * time.Second) // 결제 및 주문이 제대로 되었는지 확인한다. this.ApiURL = fmt.Sprintf("%s/order/key/%s", this.Credentials.API, orderResult.OrderID) var orderKeyResult = this.G2AOrderModel.G2AOrderKeyResult receivedDTO = this.HttpGetRequest() typeName = reflect.TypeOf(receivedDTO).Name() if typeName == "G2AErrorModel" { c.JSON(http.StatusBadRequest, receivedDTO) return } byteData, _ = json.Marshal(receivedDTO) err = json.Unmarshal(byteData, &orderKeyResult) utility.Check(err, config.ERROR_LOG_PATH_G2A) // 주문 기록 저장 this.G2AOrderModel.Insert(model.G2AOrder{ RequestKey: decryptCode, RequestOrderID: orderID, RequestOrderDetailID: orderDetailID, Status: orderDetailResult.Status, OrderID: orderResult.OrderID, ProductID: params.ProductID, Code: orderKeyResult.Key, Price: orderDetailResult.Price, Currency: orderDetailResult.Currency, TransactionID: orderPayResult.TransactionID, }) // 텔레그램 알림 utility.SendMessageToG2AOrder(func() string { msg := fmt.Sprintf("[G2A 주문완료]\n") msg += fmt.Sprintf("Order ID : %d\n", orderID) msg += fmt.Sprintf("Order Detail ID : %d\n", orderDetailID) msg += fmt.Sprintf("Status : %s\n", orderDetailResult.Status) msg += fmt.Sprintf("Code: %s\n", orderKeyResult.Key) msg += fmt.Sprintf("Price: %f\n", orderDetailResult.Price) msg += fmt.Sprintf("Currency: %s\n", orderDetailResult.Currency) return msg }()) c.JSON(http.StatusOK, gin.H{ "result": "OK", }) } // HTTP Get CALL func (this *G2A) HttpGetRequest() interface{} { req, err := http.NewRequest("GET", this.ApiURL, nil) utility.Check(err, config.ERROR_LOG_PATH_G2A) req.Header.Set("Accept", "application/json") req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash)) client := &http.Client{ Timeout: 8 * time.Second, } res, err := client.Do(req) utility.Check(err, config.ERROR_LOG_PATH_G2A) defer res.Body.Close() data, err := io.ReadAll(res.Body) utility.Check(err, config.ERROR_LOG_PATH_G2A) if res.StatusCode != 200 { return this.SetG2AError(data, nil) } var receivedDTO interface{} err = json.Unmarshal(data, &receivedDTO) utility.Check(err, config.ERROR_LOG_PATH_G2A) return receivedDTO } // HTTP Post CALL func (this *G2A) HttpPostRequest(params model.G2AOrderParams) interface{} { payloadBytes, err := json.Marshal(params) utility.Check(err, config.ERROR_LOG_PATH_G2A) body := bytes.NewReader(payloadBytes) req, err := http.NewRequest("POST", this.ApiURL, body) utility.Check(err, config.ERROR_LOG_PATH_G2A) req.Header.Set("Content-Type", "application/json") req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash)) client := &http.Client{ Timeout: 8 * time.Second, } res, err := client.Do(req) utility.Check(err, config.ERROR_LOG_PATH_G2A) defer res.Body.Close() data, err := io.ReadAll(res.Body) utility.Check(err, config.ERROR_LOG_PATH_G2A) if res.StatusCode != 200 { return this.SetG2AError(data, payloadBytes) } var receivedDTO interface{} err = json.Unmarshal(data, &receivedDTO) utility.Check(err, config.ERROR_LOG_PATH_G2A) return receivedDTO } // HTTP Put CALL func (this *G2A) HttpPutRequest() interface{} { req, err := http.NewRequest("PUT", this.ApiURL, nil) utility.Check(err, config.ERROR_LOG_PATH_G2A) req.Header.Set("Content-type", "application/json") req.Header.Set("Content-Length", "0") req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash)) fmt.Println(fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash)) client := &http.Client{ Timeout: 8 * time.Second, } res, err := client.Do(req) utility.Check(err, config.ERROR_LOG_PATH_G2A) defer res.Body.Close() data, err := io.ReadAll(res.Body) utility.Check(err, config.ERROR_LOG_PATH_G2A) if res.StatusCode != 200 { return this.SetG2AError(data, nil) } var receivedDTO interface{} err = json.Unmarshal(data, &receivedDTO) utility.Check(err, config.ERROR_LOG_PATH_G2A) return receivedDTO } // G2A 주문 API 오류 발생 처리 func (this *G2A) SetG2AError(data []byte, params interface{}) model.G2AErrorModel { var G2AError = this.G2AErrorModel err := json.Unmarshal(data, &G2AError) utility.Check(err, config.ERROR_LOG_PATH_G2A) G2AError.URL = this.ApiURL if params != nil { G2AError.Params = string(params.([]byte)) } G2AError.Save() // 텔레그램 알림 if err != nil { utility.SendMessageToG2AError(fmt.Sprintf("[G2A 주문 중 오류]\n%s", err.Error())) } return G2AError }