apple.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package apple
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "bet24.com/log"
  10. )
  11. // Receipt is information returned by Apple
  12. //
  13. // Documentation: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW10
  14. type Receipt struct {
  15. OriginalPurchaseDatePst string `json:"original_purchase_date_pst"`
  16. UniqueIdentifier string `json:"unique_identifier"`
  17. OriginalTransactionId string `json:"original_transaction_id"`
  18. Bvrs string `json:"bvrs"`
  19. AppItemId string `json:"app_item_id"`
  20. TransactionId string `json:"transaction_id"`
  21. Quantity string `json:"quantity"`
  22. UniqueVendorIdentifier string `json:"unique_vendor_identifier"`
  23. ProductId string `json:"product_id"`
  24. ItemId string `json:"item_id"`
  25. VersionExternalIdentifier string `json:"version_external_identifier"`
  26. Bid string `json:"bid"`
  27. IsInIntroOfferPeriod string `json:"is_in_intro_offer_period"`
  28. PurchaseDateMs string `json:"purchase_date_ms"`
  29. PurchaseDate string `json:"purchase_date"`
  30. IsTrialPeriod string `json:"is_trial_period"`
  31. PurchaseDatePst string `json:"purchase_date_pst"`
  32. OriginalPurchaseDate string `json:"original_purchase_date"`
  33. OriginalPurchaseDateMs string `json:"original_purchase_date_ms"`
  34. }
  35. type receiptRequestData struct {
  36. Receiptdata string `json:"receipt-data"`
  37. }
  38. const (
  39. appleSandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt"
  40. appleProductionURL string = "https://buy.itunes.apple.com/verifyReceipt"
  41. )
  42. // Simple interface to get the original error code from the error object
  43. type ErrorWithCode interface {
  44. Code() float64
  45. }
  46. type Error struct {
  47. error
  48. errCode float64
  49. }
  50. // Simple method to get the original error code from the error object
  51. func (e *Error) Code() float64 {
  52. return e.errCode
  53. }
  54. // Given receiptData (base64 encoded) it tries to connect to either the sandbox (useSandbox true) or
  55. // apples ordinary service (useSandbox false) to validate the receipt. Returns either a receipt struct or an error.
  56. func VerifyReceipt(receiptData string, useSandbox bool) (*Receipt, error, string) {
  57. return sendReceiptToApple(receiptData, verificationURL(useSandbox))
  58. }
  59. // Selects the proper url to use when talking to apple based on if we should use the sandbox environment or not
  60. func verificationURL(useSandbox bool) string {
  61. if useSandbox {
  62. return appleSandboxURL
  63. }
  64. return appleProductionURL
  65. }
  66. // Sends the receipt to apple, returns the receipt or an error upon completion
  67. func sendReceiptToApple(receiptData, url string) (*Receipt, error, string) {
  68. requestData, err := json.Marshal(receiptRequestData{receiptData})
  69. if err != nil {
  70. return nil, err, ""
  71. }
  72. toSend := bytes.NewBuffer(requestData)
  73. resp, err := http.Post(url, "application/json", toSend)
  74. if err != nil {
  75. log.Error("apple.sendReceiptToApple post fail %v ==> receiptData=%s", err, receiptData)
  76. return nil, err, ""
  77. }
  78. defer resp.Body.Close()
  79. body, err := io.ReadAll(resp.Body)
  80. //log.Debug("sendReceiptToApple body ==> %s", body)
  81. var responseData struct {
  82. Status float64 `json:"status"`
  83. ReceiptContent *Receipt `json:"receipt"`
  84. }
  85. responseData.ReceiptContent = new(Receipt)
  86. err = json.Unmarshal(body, &responseData)
  87. if err != nil {
  88. return nil, err, ""
  89. }
  90. if responseData.Status != 0 {
  91. return nil, verificationError(responseData.Status), string(body)
  92. }
  93. return responseData.ReceiptContent, nil, string(body)
  94. }
  95. // Error codes as they returned by the App Store
  96. const (
  97. UnreadableJSON = 21000 //App Store无法读取您提供的JSON对象。
  98. MalformedData = 21002 //该receipt-data属性中的数据格式错误或丢失。
  99. AuthenticationError = 21003 //收据无法认证。
  100. UnmatchedSecret = 21004 //您提供的共享密码与您帐户的文件共享密码不匹配。
  101. ServerUnavailable = 21005 //收据服务器当前不可用。
  102. SubscriptionExpired = 21006 //该收据有效,但订阅已过期。当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回。仅针对自动续订的iOS 6样式交易收据返回。
  103. SandboxReceiptOnProd = 21007 //该收据来自测试环境,但已发送到生产环境以进行验证。而是将其发送到测试环境。
  104. ProdReceiptOnSandbox = 21008 //该收据来自生产环境,但是已发送到测试环境以进行验证。而是将其发送到生产环境。
  105. ErrorReceiptNoAuth = 21010 //此收据无法授权。就像从未进行过购买一样对待。
  106. ErrorInternalMin = 21100 //21100~21199 内部数据访问错误。
  107. ErrorInternalMax = 21199 //21100~21199 内部数据访问错误。
  108. )
  109. // Generates the correct error based on a status error code
  110. func verificationError(errCode float64) error {
  111. var errorMessage string
  112. switch errCode {
  113. case UnreadableJSON:
  114. errorMessage = "The App Store could not read the JSON object you provided."
  115. case MalformedData:
  116. errorMessage = "The data in the receipt-data property was malformed."
  117. case AuthenticationError:
  118. errorMessage = "The receipt could not be authenticated."
  119. case UnmatchedSecret:
  120. errorMessage = "The shared secret you provided does not match the shared secret on file for your account."
  121. case ServerUnavailable:
  122. errorMessage = "The receipt server is not currently available."
  123. case SubscriptionExpired:
  124. errorMessage = "This receipt is valid but the subscription has expired. When this status code is returned to your server, " +
  125. "the receipt data is also decoded and returned as part of the response."
  126. case SandboxReceiptOnProd:
  127. errorMessage = "This receipt is a sandbox receipt, but it was sent to the production service for verification."
  128. case ProdReceiptOnSandbox:
  129. errorMessage = "This receipt is a production receipt, but it was sent to the sandbox service for verification."
  130. default:
  131. errorMessage = "An unknown error ocurred"
  132. }
  133. errorMessage = fmt.Sprintf("Code:%.f %s", errCode, errorMessage)
  134. return &Error{errors.New(errorMessage), errCode}
  135. }