g2a.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. package controller
  2. import (
  3. "bytes"
  4. "crawler/config"
  5. "crawler/model"
  6. "crawler/utility"
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "encoding/json"
  10. "fmt"
  11. "github.com/gin-gonic/gin"
  12. "io"
  13. "log"
  14. "net/http"
  15. "reflect"
  16. "strconv"
  17. "strings"
  18. "time"
  19. )
  20. type G2AInterface interface {
  21. Init()
  22. Products(c *gin.Context)
  23. Order(c *gin.Context)
  24. }
  25. type G2A struct {
  26. ApiURL string
  27. IsTest bool
  28. Credentials config.G2ACredential
  29. G2AProductModel model.G2AProductModel
  30. G2AReportModel model.G2AReportModel
  31. G2AOrderModel model.G2AOrderModel
  32. G2AErrorModel model.G2AErrorModel
  33. G2AErrorResponse
  34. OrderDetail model.OrderDetailModel
  35. ShopConfig model.ShopConfigModel
  36. }
  37. // G2A 오류
  38. type G2AErrorResponse struct {
  39. Status any
  40. Message any
  41. Code any
  42. }
  43. // G2A 환경설정 조회
  44. func (this *G2A) Init() {
  45. this.IsTest = this.ShopConfig.G2AIsTest()
  46. if this.IsTest == true {
  47. this.Credentials = config.G2A.Sandbox
  48. } else {
  49. switch config.Env.DeveloperEnv {
  50. case config.LOCAL:
  51. this.Credentials = config.G2A.Sandbox
  52. case config.DEV:
  53. this.Credentials = config.G2A.Export
  54. default:
  55. log.Panic("G2A 설정이 잘못되었습니다.")
  56. }
  57. }
  58. var checksum = sha256.Sum256([]byte(fmt.Sprintf("%s%s%s",
  59. this.Credentials.Client.ID, this.Credentials.Email, this.Credentials.Client.Secret,
  60. )))
  61. this.Credentials.Hash = fmt.Sprintf("%x", checksum[:])
  62. }
  63. // G2A 상품 조회
  64. func (this *G2A) Products(c *gin.Context) {
  65. this.Init()
  66. fmt.Println("G2A 상품 수집을 시작합니다.")
  67. var (
  68. result = this.G2AProductModel.G2AProductResult
  69. total, insert, update, error, page = 0, 0, 0, 0, 1
  70. startTime = time.Now()
  71. endTime float64
  72. output = func(msg string) string {
  73. s := fmt.Sprintf("Total : %d\n", total)
  74. s += fmt.Sprintf("Error: %d\n", error)
  75. s += fmt.Sprintf("Insert: %d\n", insert)
  76. s += fmt.Sprintf("Update: %d\n", update)
  77. s += fmt.Sprintf("Page: %d\n", page)
  78. s += fmt.Sprintf("소요시간: %f초\n", endTime)
  79. return msg + "\n" + s
  80. }
  81. )
  82. for {
  83. this.ApiURL = fmt.Sprintf("%s/products?page=%d", this.Credentials.API, page)
  84. receivedDTO := this.HttpGetRequest()
  85. byteData, _ := json.Marshal(receivedDTO)
  86. err := json.Unmarshal(byteData, &result)
  87. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  88. // 상품 수집
  89. if len(result.Docs) > 0 {
  90. for i, product := range result.Docs {
  91. if this.G2AProductModel.IsExists(product.ID) {
  92. update++
  93. } else {
  94. insert++
  95. }
  96. if this.IsTest {
  97. result.Docs[i].IsTest = 1
  98. } else {
  99. result.Docs[i].IsTest = 0
  100. }
  101. }
  102. err = this.G2AProductModel.Insert(result.Docs)
  103. if err != nil {
  104. error++
  105. utility.SendMessageToG2AError(output("[G2A 상품수집 중 오류]"))
  106. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  107. break
  108. }
  109. total += len(result.Docs)
  110. page++
  111. fmt.Printf("G2A 상품 수집 중... / total: %d, page: %d\n", total, page)
  112. } else {
  113. endTime = time.Since(startTime).Seconds()
  114. fmt.Printf("G2A 상품 수집 종료! / total: %d, page: %d\n", total, page)
  115. break
  116. }
  117. }
  118. // 통계 저장
  119. this.G2AReportModel.Insert(model.G2AReport{
  120. TotalCnt: total,
  121. InsertCnt: insert,
  122. UpdateCnt: update,
  123. ProcessAt: endTime,
  124. LastPage: page,
  125. })
  126. // 텔레그램 알림
  127. utility.SendMessageToG2AProduct(output("[G2A 상품수집 완료]"))
  128. c.JSON(http.StatusOK, gin.H{
  129. "total": total,
  130. "insert": insert,
  131. "update": update,
  132. "page": page,
  133. })
  134. }
  135. // G2A 품절 확인 후 상품 정보 갱신
  136. func (this *G2A) CheckOutOfStock(c *gin.Context) {
  137. this.Init()
  138. fmt.Println("G2A 상품 품절 확인")
  139. var (
  140. result = this.G2AProductModel.G2AProductResult
  141. productID = c.Query("id")
  142. inStock = "yes"
  143. )
  144. if productID == "" {
  145. c.JSON(http.StatusOK, gin.H{
  146. "inStock": "no",
  147. })
  148. return
  149. }
  150. this.ApiURL = fmt.Sprintf("%s/products?id=%s", this.Credentials.API, productID)
  151. receivedDTO := this.HttpGetRequest()
  152. byteData, _ := json.Marshal(receivedDTO)
  153. err := json.Unmarshal(byteData, &result)
  154. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  155. // 상품 정보 확인
  156. if len(result.Docs) > 0 {
  157. for _, product := range result.Docs {
  158. // 조회된 상품 정보를 한번 갱신한다.
  159. err = this.G2AProductModel.Update(product)
  160. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  161. if product.Qty <= 0 { // 재고가 0 이하면 품절이다.
  162. inStock = "no"
  163. break
  164. }
  165. }
  166. }
  167. c.JSON(http.StatusOK, gin.H{
  168. "inStock": inStock,
  169. })
  170. }
  171. // G2A 주문
  172. // Add an Order -> Pay for an order
  173. func (this *G2A) Order(c *gin.Context) {
  174. var requestKey = c.Query("requestKey")
  175. if requestKey == "" {
  176. c.JSON(http.StatusForbidden, G2AErrorResponse{
  177. Code: http.StatusForbidden,
  178. Message: "잘못된 요청입니다.",
  179. })
  180. return
  181. }
  182. // base64_decode 처리
  183. rawRequestKey, err := base64.URLEncoding.DecodeString(requestKey)
  184. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  185. requestKey = string(rawRequestKey)
  186. var (
  187. key = utility.MakeMD5(config.ENCRYPT_KEY)
  188. iv = config.ENCRYPT_IV
  189. )
  190. // 복호화
  191. decryptCode, err := utility.AesDecrypt(requestKey, key, iv)
  192. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  193. // 이미 주문한 주문건이 있는지 확인
  194. if this.G2AOrderModel.IsExists(decryptCode) {
  195. c.JSON(http.StatusOK, gin.H{
  196. "result": "OK",
  197. })
  198. return
  199. }
  200. id := strings.Split(decryptCode, "/")
  201. var (
  202. orderID, _ = strconv.Atoi(id[0])
  203. orderDetailID, _ = strconv.Atoi(id[1])
  204. productID = id[2]
  205. )
  206. if this.OrderDetail.IsExists(orderID, orderDetailID) == false {
  207. c.JSON(http.StatusNotFound, G2AErrorResponse{
  208. Code: http.StatusNotFound,
  209. Message: "비 정상적인 주문서 입니다.",
  210. })
  211. return
  212. }
  213. //orderDetail, err := this.OrderDetail.Info(orderID, orderDetailID)
  214. //utility.Check(err, config.ERROR_LOG_PATH_G2A)
  215. tmpProductID, _ := strconv.Atoi(productID)
  216. G2AProductInfo, err := this.G2AProductModel.Info(tmpProductID)
  217. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  218. var params = this.G2AOrderModel.G2AOrderParams
  219. params.ProductID = productID
  220. params.Currency = "KRW"
  221. params.MaxPrice = G2AProductInfo.RetailMinPrice
  222. if err := c.ShouldBind(&params); err != nil {
  223. c.JSON(http.StatusBadRequest, G2AErrorResponse{
  224. Code: http.StatusBadRequest,
  225. Message: err.Error(),
  226. })
  227. return
  228. }
  229. this.Init()
  230. // 주문을 요청한다.
  231. this.ApiURL = fmt.Sprintf("%s/order", this.Credentials.API)
  232. var (
  233. orderResult = this.G2AOrderModel.G2AOrderResult
  234. receivedDTO = this.HttpPostRequest(params)
  235. typeName = reflect.TypeOf(receivedDTO).Name()
  236. )
  237. if typeName == "G2AErrorModel" {
  238. c.JSON(http.StatusBadRequest, receivedDTO)
  239. return
  240. }
  241. byteData, _ := json.Marshal(receivedDTO)
  242. err = json.Unmarshal(byteData, &orderResult)
  243. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  244. // 주문 번호를 결제 처리한다.
  245. this.ApiURL = fmt.Sprintf("%s/order/pay/%s", this.Credentials.API, orderResult.OrderID)
  246. var orderPayResult = this.G2AOrderModel.G2AOrderPayResult
  247. receivedDTO = this.HttpPutRequest()
  248. typeName = reflect.TypeOf(receivedDTO).Name()
  249. if typeName == "G2AErrorModel" {
  250. c.JSON(http.StatusBadRequest, receivedDTO)
  251. return
  252. }
  253. byteData, _ = json.Marshal(receivedDTO)
  254. err = json.Unmarshal(byteData, &orderPayResult)
  255. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  256. // 결제 확인
  257. if orderPayResult.Status == false {
  258. c.JSON(http.StatusOK, gin.H{
  259. "message": "결제에 실패하였습니다.",
  260. })
  261. return
  262. }
  263. // 결제 및 주문이 제대로 되었는지 확인한다.
  264. this.ApiURL = fmt.Sprintf("%s/order/details/%s", this.Credentials.API, orderResult.OrderID)
  265. var orderDetailResult = this.G2AOrderModel.G2AOrderDetailResult
  266. receivedDTO = this.HttpGetRequest()
  267. typeName = reflect.TypeOf(receivedDTO).Name()
  268. if typeName == "G2AErrorModel" {
  269. c.JSON(http.StatusBadRequest, receivedDTO)
  270. return
  271. }
  272. byteData, _ = json.Marshal(receivedDTO)
  273. err = json.Unmarshal(byteData, &orderDetailResult)
  274. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  275. time.Sleep(1 * time.Second)
  276. // 결제 및 주문이 제대로 되었는지 확인한다.
  277. this.ApiURL = fmt.Sprintf("%s/order/key/%s", this.Credentials.API, orderResult.OrderID)
  278. var orderKeyResult = this.G2AOrderModel.G2AOrderKeyResult
  279. receivedDTO = this.HttpGetRequest()
  280. typeName = reflect.TypeOf(receivedDTO).Name()
  281. if typeName == "G2AErrorModel" {
  282. c.JSON(http.StatusBadRequest, receivedDTO)
  283. return
  284. }
  285. byteData, _ = json.Marshal(receivedDTO)
  286. err = json.Unmarshal(byteData, &orderKeyResult)
  287. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  288. // 주문 기록 저장
  289. this.G2AOrderModel.Insert(model.G2AOrder{
  290. RequestKey: decryptCode,
  291. RequestOrderID: orderID,
  292. RequestOrderDetailID: orderDetailID,
  293. Status: orderDetailResult.Status,
  294. OrderID: orderResult.OrderID,
  295. ProductID: params.ProductID,
  296. Code: orderKeyResult.Key,
  297. Price: orderDetailResult.Price,
  298. Currency: orderDetailResult.Currency,
  299. TransactionID: orderPayResult.TransactionID,
  300. })
  301. // 텔레그램 알림
  302. utility.SendMessageToG2AOrder(func() string {
  303. msg := fmt.Sprintf("[G2A 주문완료]\n")
  304. msg += fmt.Sprintf("Order ID : %d\n", orderID)
  305. msg += fmt.Sprintf("Order Detail ID : %d\n", orderDetailID)
  306. msg += fmt.Sprintf("Status : %s\n", orderDetailResult.Status)
  307. msg += fmt.Sprintf("Code: %s\n", orderKeyResult.Key)
  308. msg += fmt.Sprintf("Price: %f\n", orderDetailResult.Price)
  309. msg += fmt.Sprintf("Currency: %s\n", orderDetailResult.Currency)
  310. return msg
  311. }())
  312. c.JSON(http.StatusOK, gin.H{
  313. "result": "OK",
  314. })
  315. }
  316. // HTTP Get CALL
  317. func (this *G2A) HttpGetRequest() interface{} {
  318. req, err := http.NewRequest("GET", this.ApiURL, nil)
  319. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  320. req.Header.Set("Accept", "application/json")
  321. req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash))
  322. client := &http.Client{
  323. Timeout: 8 * time.Second,
  324. }
  325. res, err := client.Do(req)
  326. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  327. defer res.Body.Close()
  328. data, err := io.ReadAll(res.Body)
  329. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  330. if res.StatusCode != 200 {
  331. return this.SetG2AError(data, nil)
  332. }
  333. var receivedDTO interface{}
  334. err = json.Unmarshal(data, &receivedDTO)
  335. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  336. return receivedDTO
  337. }
  338. // HTTP Post CALL
  339. func (this *G2A) HttpPostRequest(params model.G2AOrderParams) interface{} {
  340. payloadBytes, err := json.Marshal(params)
  341. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  342. body := bytes.NewReader(payloadBytes)
  343. req, err := http.NewRequest("POST", this.ApiURL, body)
  344. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  345. req.Header.Set("Content-Type", "application/json")
  346. req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash))
  347. client := &http.Client{
  348. Timeout: 8 * time.Second,
  349. }
  350. res, err := client.Do(req)
  351. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  352. defer res.Body.Close()
  353. data, err := io.ReadAll(res.Body)
  354. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  355. if res.StatusCode != 200 {
  356. return this.SetG2AError(data, payloadBytes)
  357. }
  358. var receivedDTO interface{}
  359. err = json.Unmarshal(data, &receivedDTO)
  360. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  361. return receivedDTO
  362. }
  363. // HTTP Put CALL
  364. func (this *G2A) HttpPutRequest() interface{} {
  365. req, err := http.NewRequest("PUT", this.ApiURL, nil)
  366. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  367. req.Header.Set("Content-type", "application/json")
  368. req.Header.Set("Content-Length", "0")
  369. req.Header.Add("Authorization", fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash))
  370. fmt.Println(fmt.Sprintf("%s, %s", this.Credentials.Client.ID, this.Credentials.Hash))
  371. client := &http.Client{
  372. Timeout: 8 * time.Second,
  373. }
  374. res, err := client.Do(req)
  375. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  376. defer res.Body.Close()
  377. data, err := io.ReadAll(res.Body)
  378. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  379. if res.StatusCode != 200 {
  380. return this.SetG2AError(data, nil)
  381. }
  382. var receivedDTO interface{}
  383. err = json.Unmarshal(data, &receivedDTO)
  384. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  385. return receivedDTO
  386. }
  387. // G2A 주문 API 오류 발생 처리
  388. func (this *G2A) SetG2AError(data []byte, params interface{}) model.G2AErrorModel {
  389. var G2AError = this.G2AErrorModel
  390. err := json.Unmarshal(data, &G2AError)
  391. utility.Check(err, config.ERROR_LOG_PATH_G2A)
  392. G2AError.URL = this.ApiURL
  393. if params != nil {
  394. G2AError.Params = string(params.([]byte))
  395. }
  396. G2AError.Save()
  397. // 텔레그램 알림
  398. if err != nil {
  399. utility.SendMessageToG2AError(fmt.Sprintf("[G2A 주문 중 오류]\n%s", err.Error()))
  400. }
  401. return G2AError
  402. }