package apple import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "bet24.com/log" ) // Receipt is information returned by Apple // // Documentation: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW10 type Receipt struct { OriginalPurchaseDatePst string `json:"original_purchase_date_pst"` UniqueIdentifier string `json:"unique_identifier"` OriginalTransactionId string `json:"original_transaction_id"` Bvrs string `json:"bvrs"` AppItemId string `json:"app_item_id"` TransactionId string `json:"transaction_id"` Quantity string `json:"quantity"` UniqueVendorIdentifier string `json:"unique_vendor_identifier"` ProductId string `json:"product_id"` ItemId string `json:"item_id"` VersionExternalIdentifier string `json:"version_external_identifier"` Bid string `json:"bid"` IsInIntroOfferPeriod string `json:"is_in_intro_offer_period"` PurchaseDateMs string `json:"purchase_date_ms"` PurchaseDate string `json:"purchase_date"` IsTrialPeriod string `json:"is_trial_period"` PurchaseDatePst string `json:"purchase_date_pst"` OriginalPurchaseDate string `json:"original_purchase_date"` OriginalPurchaseDateMs string `json:"original_purchase_date_ms"` } type receiptRequestData struct { Receiptdata string `json:"receipt-data"` } const ( appleSandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt" appleProductionURL string = "https://buy.itunes.apple.com/verifyReceipt" ) // Simple interface to get the original error code from the error object type ErrorWithCode interface { Code() float64 } type Error struct { error errCode float64 } // Simple method to get the original error code from the error object func (e *Error) Code() float64 { return e.errCode } // Given receiptData (base64 encoded) it tries to connect to either the sandbox (useSandbox true) or // apples ordinary service (useSandbox false) to validate the receipt. Returns either a receipt struct or an error. func VerifyReceipt(receiptData string, useSandbox bool) (*Receipt, error, string) { return sendReceiptToApple(receiptData, verificationURL(useSandbox)) } // Selects the proper url to use when talking to apple based on if we should use the sandbox environment or not func verificationURL(useSandbox bool) string { if useSandbox { return appleSandboxURL } return appleProductionURL } // Sends the receipt to apple, returns the receipt or an error upon completion func sendReceiptToApple(receiptData, url string) (*Receipt, error, string) { requestData, err := json.Marshal(receiptRequestData{receiptData}) if err != nil { return nil, err, "" } toSend := bytes.NewBuffer(requestData) resp, err := http.Post(url, "application/json", toSend) if err != nil { log.Error("apple.sendReceiptToApple post fail %v ==> receiptData=%s", err, receiptData) return nil, err, "" } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) //log.Debug("sendReceiptToApple body ==> %s", body) var responseData struct { Status float64 `json:"status"` ReceiptContent *Receipt `json:"receipt"` } responseData.ReceiptContent = new(Receipt) err = json.Unmarshal(body, &responseData) if err != nil { return nil, err, "" } if responseData.Status != 0 { return nil, verificationError(responseData.Status), string(body) } return responseData.ReceiptContent, nil, string(body) } // Error codes as they returned by the App Store const ( UnreadableJSON = 21000 //App Store无法读取您提供的JSON对象。 MalformedData = 21002 //该receipt-data属性中的数据格式错误或丢失。 AuthenticationError = 21003 //收据无法认证。 UnmatchedSecret = 21004 //您提供的共享密码与您帐户的文件共享密码不匹配。 ServerUnavailable = 21005 //收据服务器当前不可用。 SubscriptionExpired = 21006 //该收据有效,但订阅已过期。当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回。仅针对自动续订的iOS 6样式交易收据返回。 SandboxReceiptOnProd = 21007 //该收据来自测试环境,但已发送到生产环境以进行验证。而是将其发送到测试环境。 ProdReceiptOnSandbox = 21008 //该收据来自生产环境,但是已发送到测试环境以进行验证。而是将其发送到生产环境。 ErrorReceiptNoAuth = 21010 //此收据无法授权。就像从未进行过购买一样对待。 ErrorInternalMin = 21100 //21100~21199 内部数据访问错误。 ErrorInternalMax = 21199 //21100~21199 内部数据访问错误。 ) // Generates the correct error based on a status error code func verificationError(errCode float64) error { var errorMessage string switch errCode { case UnreadableJSON: errorMessage = "The App Store could not read the JSON object you provided." case MalformedData: errorMessage = "The data in the receipt-data property was malformed." case AuthenticationError: errorMessage = "The receipt could not be authenticated." case UnmatchedSecret: errorMessage = "The shared secret you provided does not match the shared secret on file for your account." case ServerUnavailable: errorMessage = "The receipt server is not currently available." case SubscriptionExpired: errorMessage = "This receipt is valid but the subscription has expired. When this status code is returned to your server, " + "the receipt data is also decoded and returned as part of the response." case SandboxReceiptOnProd: errorMessage = "This receipt is a sandbox receipt, but it was sent to the production service for verification." case ProdReceiptOnSandbox: errorMessage = "This receipt is a production receipt, but it was sent to the sandbox service for verification." default: errorMessage = "An unknown error ocurred" } errorMessage = fmt.Sprintf("Code:%.f %s", errCode, errorMessage) return &Error{errors.New(errorMessage), errCode} }