레이블이 receipt인 게시물을 표시합니다. 모든 게시물 표시
레이블이 receipt인 게시물을 표시합니다. 모든 게시물 표시

2014년 5월 4일 일요일

티스토어(T-Store) 인앱 영수증 검증 C#버전(t-store in-app purchase receipt verification)

 던전워즈
c#으로 작성된 서버 또는 유니티 등에서 티스토어의 인앱 결제 영수증 검증을 다음과 같이 하면된다.

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

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
 
public static class COneStoreReceiptVerify
{
    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;
        }
 
//----------------------------------------------------------
readonly string TSTORE_RECEIPT_VERIFY_URL_PRODUCT = "https://iap.tstore.co.kr/digitalsignconfirm.iap";  //실서비스용서버주소
        readonly string TSTORE_RECEIPT_VERIFY_URL_DEV = "https://iapdev.tstore.co.kr/digitalsignconfirm.iap"//개발용서버주소
        readonly string TSTORE_RECEIPT_VERIFY_FORMAT = "{{\"txid\" : \"{0}\", \"appid\" : \"{1}\", \"signdata\" : \"{2}\"}}";
        //TStore 영수증 검중구현, 
//bProduct : 서비스/개발용 서버중 어느 서버에서 검증할지
//strAppID : 티스토어에 배포한 프로그램에 할당받은 ID
//strTXID : 인앱 결제후 결과 값으로 넘어온 것중 txid
//strReceipt : 인앱결제 후 결과 값으로 넘어온 영수증 정보
//리턴값이 true인경우 영수증검증에 성공한것이다.
//애플의 영수증을 검증하는 것처럼 먼저 bProduct에 true를 해서 영수증검증을 해보고 만일 실패 하는경우
//false를 줘서 검증을 시도하도록 하면 된다.
        bool VerifyTStoreReceipt(bool bProduct, string strAppID, string strTXID, string strReceipt)
        {
            try
            {
                // Verify the receipt with Apple
                string postString = String.Format(TSTORE_RECEIPT_VERIFY_FORMAT, strTXID, strAppID, strReceipt);
                ASCIIEncoding ascii = new ASCIIEncoding();
                byte[] postBytes = ascii.GetBytes(postString);
                HttpWebRequest request;
 
 
                if (bProduct)
                    request = WebRequest.Create(TSTORE_RECEIPT_VERIFY_URL_PRODUCT) as HttpWebRequest;
                else
                    request = WebRequest.Create(TSTORE_RECEIPT_VERIFY_URL_DEV) 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();
                string strDetail = fd["detail"].ToString();
 
                // Receipt not valid
                if (strResult == null || strResult != "0" || strDetail == null || strDetail != "0000")
                {
#if DEBUG
                    CLogMng.Instance().Debug("tstore purchase failed : " + sb.ToString());
                    CLogMng.Instance().Debug("txid : " + strTXID);
                    CLogMng.Instance().Debug("signdata : " + strReceipt);
#endif
                    return false;
                }
            }
            catch// (Exception ex)
            {
                // We crashed and burned -- do something intelligent
                return false;
            }
 
            return true;
        }
}
cs

구글 인앱 영수증 검증 c# 버전(google in-app purchase receipt verification)

 던전워즈

첨부된 바운시캐슬 암호화 라이브러리를 참조에 추가하고 사용해야 한다.
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
using System;
using System.Security.Cryptography;
using System.IO;
using System.Text;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
 
/*
사용법
GoogleSignatureVerify gsv = new GoogleSignatureVerify("발급받은 API키");
if (gsv.Verify("결제데이터", 사인데이터))
{
//성공시 처리
}
*/
public class GoogleSignatureVerify
    {
        RSAParameters _rsaKeyInfo;
 
 
        public GoogleSignatureVerify(String GooglePublicKey)
        {
            RsaKeyParameters rsaParameters = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(GooglePublicKey));
 
            byte[] rsaExp = rsaParameters.Exponent.ToByteArray();
            byte[] Modulus = rsaParameters.Modulus.ToByteArray();
 
            // Microsoft RSAParameters modulo wants leading zero's removed so create new array with leading zero's removed
            int Pos = 0;
            for (int i = 0; i < Modulus.Length; i++)
            {
                if (Modulus[i] == 0)
                {
                    Pos++;
                }
                else
                {
                    break;
                }
            }
            byte[] rsaMod = new byte[Modulus.Length - Pos];
            Array.Copy(Modulus, Pos, rsaMod, 0, Modulus.Length - Pos);
 
            // Fill the Microsoft parameters
            _rsaKeyInfo = new RSAParameters()
            {
                Exponent = rsaExp,
                Modulus = rsaMod
            };
        }
 
        public bool Verify(String Message, String Signature)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(_rsaKeyInfo);
                return rsa.VerifyData(Encoding.ASCII.GetBytes(Message), "SHA1", Convert.FromBase64String(Signature));
            }
        }
    }
cs

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;
}