package main import ( "context" "encoding/json" "io" "net/http" "net/url" "strings" "github.com/awa/go-iap/appstore/api" "github.com/labstack/echo/v4" "go.uber.org/zap" ) // 处理appstore的回调 func IapCallbackHandler(c echo.Context) error { // 获取请求体 body, err := io.ReadAll(c.Request().Body) // {"signedPayload":"..."} if err != nil { logger.Error("Failed to read request body", zap.Error(err)) return echo.NewHTTPError(http.StatusBadRequest, "read body error.") //return c.JSON(http.StatusInternalServerError, "Failed to read request body") } // App Store Server Notification Request JSON String var request AppStoreServerRequest err2 := json.Unmarshal([]byte(body), &request) // bind byte to header structure if err2 != nil { logger.Error("AppStoreServerRequest Unmarshal error.", zap.Error(err)) return echo.NewHTTPError(http.StatusBadRequest, "Failed to read request body") } // Apple Root CA - G3 Root certificate // for details: https://www.apple.com/certificateauthority/ // you need download it and covert it to a valid pem file in order to verify X5c certificates // `openssl x509 -in AppleRootCA-G3.cer -out cert.pem` appStoreServerNotification, err := IAP_Notify_New(request.SignedPayload, IAP_ROOT_CERT) if err != nil { logger.Error("IAP_Notify_New error.", zap.Error(err)) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to decode body") } // 打印字段,为了显示方便,把加密串替换掉 appStoreServerNotification.Payload.Data.SignedRenewalInfo = "..." appStoreServerNotification.Payload.Data.SignedTransactionInfo = "..." logger.Debug("appStoreServerNotification", zap.Any("appStoreServerNotification", appStoreServerNotification)) // 打印日志 //buff, _ := json.Marshal(&appStoreServerNotification) //fmt.Println(string(buff)) UpdateOrderByNotify(appStoreServerNotification) setResponse(c, nil) return nil } // 处理从客户端过来的订单验证请求 func IapVerify(c echo.Context) error { var request struct { TransID string `json:"transid" form:"transid"` AppAccountToken string `json:"appaccounttoken" form:"appaccounttoken"` Env string `json:"env" form:"env"` ProductID string `json:"productid" form:"productid"` ReceiptData string `json:"receiptdata" form:"receiptdata"` } if err := c.Bind(&request); err != nil { logger.Error("read param error.", zap.Error(err)) return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } // 验证 receiptdata 是否为有效的 JSON 字符串 var jsonObj interface{} if err := json.Unmarshal([]byte(request.ReceiptData), &jsonObj); err != nil { logger.Debug("receiptdata from request", zap.Any("receiptdata", request.ReceiptData)) // 打印日志 } else { logger.Error("receiptdata from request", zap.Any("receiptdata", request.ReceiptData)) // 打印日志 } var isSandBox = true // 忽略大小写进行比较 if strings.EqualFold(request.Env, "Production") { isSandBox = false } cfg := &api.StoreConfig{ KeyContent: []byte(IAP_ACCOUNT_KEY), // Loads a .p8 certificate KeyID: IAP_KEY_ID, // Your private key ID from App Store Connect (Ex: 2X9R4HXF34) BundleID: IAP_BUNDLEID, // Your app’s bundle ID Issuer: IAP_ISSUER, // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a") Sandbox: isSandBox, // default is Production } client := api.NewStoreClient(cfg) ctx := context.Background() response, err := client.GetTransactionInfo(ctx, request.TransID) if err != nil { logger.Error("GetTransactionInfo error.", zap.Error(err)) return echo.NewHTTPError(http.StatusInternalServerError, "GetTransactionInfo error") } transantion, err := client.ParseSignedTransaction(response.SignedTransactionInfo) if err != nil { logger.Error("ParseSignedTransaction error.", zap.Error(err)) return echo.NewHTTPError(http.StatusInternalServerError, "ParseSignedTransaction error") } logger.Debug("transantion", zap.Any("transantion", transantion), zap.Any("request", request)) // 打印日志 //buff, _ := json.Marshal(&transantion) //fmt.Println(string(buff)) if transantion.TransactionID != request.TransID { logger.Error("transactionId not match.", zap.Any("transantion.TransactionID", transantion.TransactionID), zap.Any("request.TransID", request.TransID)) return echo.NewHTTPError(http.StatusInternalServerError, "transactionId not match") } // 写入DB GID, _ := c.Get(KEY_GID).(int) errDB := UpdateOrderByVerify(GID, request.AppAccountToken, transantion.OriginalTransactionId, transantion) if errDB != nil { logger.Error("UpdateOrderByVerify error.", zap.Error(errDB)) return echo.NewHTTPError(http.StatusInternalServerError, "UpdateOrderByVerify error") } setResponse(c, map[string]string{"ret": "ok"}) return nil } // 查询订单历史信息,通常是内部服务发起 func IapHistory(c echo.Context) error { var request struct { OriginTransID string `json:"origintransid" form:"origintransid"` Lang string `json:"lang" form:"lang"` } if err := c.Bind(&request); err != nil { logger.Error("read param error.", zap.Error(err)) return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } cfg := &api.StoreConfig{ KeyContent: []byte(IAP_ACCOUNT_KEY), // Loads a .p8 certificate KeyID: IAP_KEY_ID, // Your private key ID from App Store Connect (Ex: 2X9R4HXF34) BundleID: IAP_BUNDLEID, // Your app’s bundle ID Issuer: IAP_ISSUER, // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a") Sandbox: true, // default is Production } client := api.NewStoreClient(cfg) query := &url.Values{} query.Set("productType", "AUTO_RENEWABLE") //query.Set("productType", "NON_CONSUMABLE") ctx := context.Background() responses, err := client.GetTransactionHistory(ctx, request.OriginTransID, query) if err != nil { logger.Error("GetTransactionHistory error.", zap.Error(err)) return echo.NewHTTPError(http.StatusInternalServerError, "GetTransactionHistory error") } // 由于接口字段中有HasMore,所以 responses 是个数组,每个 responses 中的 transantions 也是数组 var allTransactions []*api.JWSTransaction for _, response := range responses { transantions, err := client.ParseSignedTransactions(response.SignedTransactions) if err != nil { logger.Error("ParseSignedTransactions error.", zap.Error(err)) return echo.NewHTTPError(http.StatusInternalServerError, "ParseSignedTransactions error") } allTransactions = append(allTransactions, transantions...) logger.Debug("transantions", zap.Any("transantions", transantions)) // 打印 //buff, _ := json.Marshal(&transantions) //fmt.Println(string(buff)) } setResponse(c, allTransactions) //setResponse(c, map[string]string{"ret": "ok"}) return nil }