2014년 5월 4일 일요일

iOS인앱 영수증 검증 루틴 c#버전(iOS in-app purchase receipt verification)


 던전워즈
유니티에서 사용하고자 하는경우 아래 코드를 추가하시고
InitCertificateValidationCallback()을 영수증 검증 루틴 실행전 한번 호출해줄필요가 있습니다.

public static void InitCertificateValidationCallback()
        {
            ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate);
        }

        static bool ValidateServerCertificate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }



iOS인앱 결제 후 영수증 검증 루틴이다.
첨부된 JSON라이브러리를 c#프로젝트의 참조에 추가할 필요가 있다.
VerifyIOSReceipt 메소드의 리턴값을 판별해서 
처리하면된다.

IOS_RV_SUCCESS = 영수증 검증 성공
IOS_RV_FAIL_RETRY  = 영수증 검증에 실패 했다, 샌드박스모드에서 다시 검증이 필요하다.
IOS_RV_FAIL  = 유효하지 않은 영수증이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System.Text;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
public class CIOSReceiptVerificationMng
{
    public const string IOS_PRODUCT_URL = "https://buy.itunes.apple.com/verifyReceipt";
    public const string IOS_SENDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
   public const string PACKAGE_NAME = "your Bundle Identifier"; //번들 ID를 입력합니다.
    public const int IOS_RV_SUCCESS = 0;    //ios영수증 검증 결과 성공
    public const int IOS_RV_FAIL_RETRY = 1;    //샌드박스에서 재검증필요
    public const int IOS_RV_FAIL = -1;    //검증 실패
    /*ios영수증 검증
itemID = 해당 영수증으로 결제한 아이템의 ID
receiptData = 영수증 데이터
bProduct = 프로덕트에서 검증할지 샌드박스에서 검증할지, 기본적으로 프로덕트에서 검증하고 리턴값이 IOS_RV_FAIL_RETRY  인경우에 샌드박스에서 검증한다.
*/
    public static int VerifyIOSReceipt(ref string itemID, string receiptData, bool bProduct)
    {
        try
        {
            itemID = null;
            // Verify the receipt with Apple
            string postString = string.Format("{{ \"receipt-data\" : \"{0}\" }}", receiptData);
            ASCIIEncoding ascii = new ASCIIEncoding();
            byte[] postBytes = ascii.GetBytes(postString);
            HttpWebRequest request;
            if (bProduct)
                request = WebRequest.Create(IOS_PRODUCT_URL) as HttpWebRequest;
            else
                request = WebRequest.Create(IOS_SENDBOX_URL) as HttpWebRequest;
            request.Method = "POST";
            request.ContentType = "application/json";
            request.ContentLength = postBytes.Length;
            Stream postStream = request.GetRequestStream();
            postStream.Write(postBytes, 0, postBytes.Length);
            postStream.Close();
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            StringBuilder sb = new StringBuilder();
            byte[] buf = new byte[8192];
            Stream resStream = response.GetResponseStream();
            string tempString = null;
            int count = 0;
            do
            {
                count = resStream.Read(buf, 0, buf.Length);
                if (count != 0)
                {
                    tempString = Encoding.ASCII.GetString(buf, 0, count);
                    sb.Append(tempString);
                }
            } while (count > 0);
            var fd = JObject.Parse(sb.ToString());
            try
            {
                resStream.Close();
                response.Close();
            }
            catch
            {
            }
            string strResult = fd["status"].ToString();
            // Receipt not valid
            if (strResult != "0")
            {
                if (strResult == "21007")
                    return IOS_RV_FAIL_RETRY;
                // Error out
                return IOS_RV_FAIL;
            }
            // Product ID does not match what we expected
            var receipt = fd["receipt"];
            /*
            if (String.Compare(receipt["product_id"].ToString().Replace("\"", "").Trim(), itemID.Trim(), true) != 0)
            {
                // Error out
                return IOS_RV_FAIL;
            }
             * */
            //제품 ID정보를 저장함
            itemID = receipt["product_id"].ToString().Replace("\"""").Trim();
            // This product was not sold by the right app
            if (String.Compare(receipt["bid"].ToString().Replace("\"""").Trim(), PACKAGE_NAME, true!= 0)
            {
                // Error out
                return IOS_RV_FAIL;
            }
            /*
            // This transaction didn't occur within 24 hours in either direction; somebody is reusing a receipt
            DateTime transDate = DateTime.SpecifyKind(DateTime.Parse(receipt["purchase_date"].ToString().Replace("\"", "").Replace("Etc/GMT", "")), DateTimeKind.Utc);
            TimeSpan delay = DateTime.UtcNow - transDate;
            if (delay.TotalHours > 24 || delay.TotalHours < -24)
            {
                // Error out
                return false;
            }
            */
            // Perform the purchase -- all my purchases are server-side only, which is a very secure way of doing things
            // Success!
        }
        catch// (Exception ex)
        {
            // We crashed and burned -- do something intelligent
            return IOS_RV_FAIL;
        }
        return IOS_RV_SUCCESS;
    }
public static bool CheckReceipt(string strItemID, string strReceipt)
{
      //일단 프로덕션에서 검증을 해봅니다.
     int ret = CIOSReceiptVerificationMng.VerifyIOSReceipt(strItemID , strReceipt , true);
    //성공
     if (ret == IOS_RV_SUCCESS)
          return true;
      //만일 샌드 박스용영수증이라면 센드박스에서 다시 검증합니다.
    if (ret == IOS_RV_FAIL_RETRY)
   {
         ret = CIOSReceiptVerificationMng.VerifyIOSReceipt(strItemID , strReceipt , false);
         if (ret !=  IOS_RV_SUCCESS)
            return false;
         return true;
    }
    //영수증 검증실패(정상구매가 아니거나 애플 서버연결에 실패 했을 수 있습니다.)
    return false;
}
}
cs

iOS결제 후 넘어온 영수증이나 아이템 데이터 같은것을 받은 다음에 위클래스를 통해서 정상적인 결제인지 검증합니다.

//string strItemID = "결제한 아이템ID";
//string strReceipt = "결제후 넘겨받은 영수증"

bool CheckReceipt(string strItemID, string strReceipt)
{
      //일단 프로덕션에서 검증을 해봅니다.
     int ret = CIOSReceiptVerificationMng.VerifyIOSReceipt(strItemID , strReceipt , true);

    //성경
     if (ret == IOS_RV_SUCCESS)
          return true;

      //만일 샌드 박스용영수증이라면 센드박스에서 다시 검증합니다.
    if (ret == IOS_RV_FAIL_RETRY)
   {
         ret = CIOSReceiptVerificationMng.VerifyIOSReceipt(strItemID , strReceipt , false);
         if (ret !=  IOS_RV_SUCCESS)
            return false;
         return true;
    }

    //영수증 검증실패(정상구매가 아니거나 애플 서버연결에 실패 했을 수 있습니다.)
    return false;
}





댓글 4개:

  1. PACKAGE_NAME 에는 어떤 값이 들어 가나요?

    답글삭제
  2. Bundle Identifier를 의미합니다.

    답글삭제
  3. 소스상에서 send하는 부분이 어딘가요?

    답글삭제
    답글
    1. send하는 부분이라는게 어떤 말씀인지 모르겠네요. 일단 ios에서 결제하고 결제 성공시에 넘어온 영수증데이터와 아이템id를 서버같은곳에 전송하신후에 검증하실때 위코드를 사용하시면됩니다.
      본문내용에 추가 해놓겠습니다.

      삭제