Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d167ac1a5 | |||
| 4b88aefda5 | |||
| 62fa34fffb | |||
| 8414aff344 | |||
| e0f6e608e7 | |||
| 11e072e211 | |||
| b60c954624 | |||
| c92634081e | |||
| fbfe02b5f6 | |||
| bac6b5676b | |||
| 94f276769f | |||
| 4ed348bb1b | |||
| 712b3f7da2 | |||
| 5354251ea5 | |||
| ec53640acb | |||
| ee59170931 | |||
| c992b9705b | |||
| ff9fd778a3 | |||
| 1877c0ae24 | |||
| 7ef252fde0 | |||
| 12a8101bb0 | |||
| f94fdb06d8 | |||
| c371d6eb78 | |||
| 6d5e856650 |
@@ -104,6 +104,7 @@ type (
|
|||||||
|
|
||||||
CancellationDate
|
CancellationDate
|
||||||
CancellationReason string `json:"cancellation_reason,omitempty"`
|
CancellationReason string `json:"cancellation_reason,omitempty"`
|
||||||
|
IsUpgraded string `json:"is_upgraded,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Receipt type has whole data of receipt
|
// The Receipt type has whole data of receipt
|
||||||
@@ -149,6 +150,18 @@ type (
|
|||||||
PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info,omitempty"`
|
PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info,omitempty"`
|
||||||
IsRetryable bool `json:"is-retryable,omitempty"`
|
IsRetryable bool `json:"is-retryable,omitempty"`
|
||||||
}
|
}
|
||||||
|
// The IAPLatestResponse type has the response properties
|
||||||
|
// If you use latest_receipt as token to verify, response should be like following struct
|
||||||
|
IAPLatestResponse struct {
|
||||||
|
Status int `json:"status,omitempty"`
|
||||||
|
Receipt InApp `json:"receipt"`
|
||||||
|
LatestReceiptInfo InApp `json:"latest_receipt_info,omitempty"`
|
||||||
|
LatestExpiredReceiptInfo InApp `json:"latest_expired_receipt_info,omitempty"`
|
||||||
|
LatestReceipt string `json:"latest_receipt,omitempty"`
|
||||||
|
SubscriptionAutoRenewStatus string `json:"auto_renew_status,omitempty"`
|
||||||
|
SubscriptionAutoRenewProductID string `json:"auto_renew_product_id,omitempty"`
|
||||||
|
SubscriptionRetryFlag string `json:"is_in_billing_retry_period,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// The HttpStatusResponse struct contains the status code returned by the store
|
// The HttpStatusResponse struct contains the status code returned by the store
|
||||||
// Used as a workaround to detect when to hit the production appstore or sandbox appstore regardless of receipt type
|
// Used as a workaround to detect when to hit the production appstore or sandbox appstore regardless of receipt type
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ type SubscriptionNotification struct {
|
|||||||
AutoRenewStatus string `json:"auto_renew_status"` // false or true
|
AutoRenewStatus string `json:"auto_renew_status"` // false or true
|
||||||
AutoRenewProductID string `json:"auto_renew_product_id"`
|
AutoRenewProductID string `json:"auto_renew_product_id"`
|
||||||
|
|
||||||
|
// HACK (msyrus): Separate Subscriptiton Notification from Notification verification response
|
||||||
|
Status int `json:"status,omitempty"`
|
||||||
|
Receipt NotificationReceipt `json:"recipt"`
|
||||||
|
SubscriptionRetryFlag string `json:"is_in_billing_retry_period"`
|
||||||
|
|
||||||
// Posted if the notification_type is RENEWAL or INTERACTIVE_RENEWAL, and only if the renewal is successful.
|
// Posted if the notification_type is RENEWAL or INTERACTIVE_RENEWAL, and only if the renewal is successful.
|
||||||
// Posted also if the notification_type is INITIAL_BUY.
|
// Posted also if the notification_type is INITIAL_BUY.
|
||||||
// Not posted for notification_type CANCEL.
|
// Not posted for notification_type CANCEL.
|
||||||
|
|||||||
+14
-14
@@ -96,64 +96,64 @@ func NewWithClient(client *http.Client) *Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify sends receipts and gets validation result
|
// Verify sends receipts and gets validation result
|
||||||
func (c *Client) Verify(ctx context.Context, reqBody IAPRequest, result interface{}) error {
|
func (c *Client) Verify(ctx context.Context, reqBody IAPRequest, result interface{}) (Environment, error) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
if err := json.NewEncoder(b).Encode(reqBody); err != nil {
|
if err := json.NewEncoder(b).Encode(reqBody); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", c.ProductionURL, b)
|
req, err := http.NewRequest("POST", c.ProductionURL, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", ContentType)
|
req.Header.Set("Content-Type", ContentType)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
resp, err := c.httpCli.Do(req)
|
resp, err := c.httpCli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
return c.parseResponse(resp, result, ctx, reqBody)
|
return c.parseResponse(resp, result, ctx, reqBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) parseResponse(resp *http.Response, result interface{}, ctx context.Context, reqBody IAPRequest) error {
|
func (c *Client) parseResponse(resp *http.Response, result interface{}, ctx context.Context, reqBody IAPRequest) (Environment, error) {
|
||||||
// Read the body now so that we can unmarshal it twice
|
// Read the body now so that we can unmarshal it twice
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(buf, &result)
|
err = json.Unmarshal(buf, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL
|
// https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL
|
||||||
var r StatusResponse
|
var r StatusResponse
|
||||||
err = json.Unmarshal(buf, &r)
|
err = json.Unmarshal(buf, &r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if r.Status == 21007 {
|
if r.Status == 21007 {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
if err := json.NewEncoder(b).Encode(reqBody); err != nil {
|
if err := json.NewEncoder(b).Encode(reqBody); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", c.SandboxURL, b)
|
req, err := http.NewRequest("POST", c.SandboxURL, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", ContentType)
|
req.Header.Set("Content-Type", ContentType)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
resp, err := c.httpCli.Do(req)
|
resp, err := c.httpCli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
// 21007 is found when the receipt is from the test environment
|
||||||
return json.NewDecoder(resp.Body).Decode(result)
|
return Sandbox, json.NewDecoder(resp.Body).Decode(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return Production, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user