2

Here is the code I am using, simplified for this example:

Public PackageName As String = "com.yourdomain.app"
Public InAppPurchaseProductID As String = "com.yourdomain.app.subscriptionXYZ"
Public PublicLicenseKey As String = "YOUR_LONG_ENCODED_LICENSE_KEY"
Public ConsolePrivateKeyCertificateData As Byte() 'method for reading p12 file contents left out for simplicity
Public ConsoleCertificate As X509Certificate2 = New X509Certificate2(ConsolePrivateKeyCertificateData, "notasecret", X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.Exportable)
Public ConsoleServiceAccountEmail As String = "console-generated-account-here@developer.gserviceaccount.com"
Public ConsoleApplicationName As String = "I just realized this may be my issue. Not sure what this value should be!"

Public Function VerifySubscriptionReceipt(ByRef expirationDate As DateTime,
                                          ByRef purchaseDate As DateTime,
                                          token As String,
                                          purchasedProductID As String,
                                          signature As String,
                                          rawJSONData As String) As Boolean?

    'First: basic check to verify the receipt matches the product you will deliver
    If Not purchasedProductID = InAppPurchaseProductID Then
        Return Nothing
    End If

    'Second: verify receipt signature
    Dim rkp As RsaKeyParameters = CType(PublicKeyFactory.CreateKey(Convert.FromBase64String(PublicLicenseKey)), RsaKeyParameters)
    Dim rsaParams As New RSAParameters
    rsaParams.Modulus = rkp.Modulus.ToByteArrayUnsigned
    rsaParams.Exponent = rkp.Exponent.ToByteArrayUnsigned
    Dim rsaProvider As New RSACryptoServiceProvider
    rsaProvider.ImportParameters(rsaParams)

    If Not rsaProvider.VerifyData(Encoding.UTF8.GetBytes(rawJSONData), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature)) Then
        Return Nothing
    End If

    'Third: check subscription status
    Dim result As SubscriptionPurchase
    Dim serviceInitializer As New AndroidPublisherService.Initializer With
        {
            .HttpClientInitializer = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(ConsoleServiceAccountEmail) With
                                                                  {
                                                                      .Scopes = New String() {"https://www.googleapis.com/auth/androidpublisher"}
                                                                  }.FromCertificate(ConsoleCertificate)),
            .ApplicationName = ConsoleApplicationName
        }

    Using service As New AndroidPublisherService(serviceInitializer)

        '*** Error thrown on this line. Google returning 403 Forbidden ***
        result = service.Purchases.Get(PackageName, InAppPurchaseProductID, token).Execute

    End Using

    If Not result.AutoRenewing.HasValue And
            result.ValidUntilTimestampMsec.HasValue And
            result.InitiationTimestampMsec.HasValue Then
        Throw New KeyNotFoundException("The subscription does not appear to be valid. (token: " & token & ")")
    End If

    expirationDate = New DateTime(result.ValidUntilTimestampMsec.Value)
    purchaseDate = New DateTime(result.InitiationTimestampMsec.Value)

    Return result.AutoRenewing.Value

End Function

So in reviewing the code for the example, I realized the likely problem is that I don't really know what the ConsoleApplicationName should be. I have tried using the "Project Name" and the "Project ID" from the Google Developer Console, but neither made a difference.

The code runs until Google returns the 403 exception.

maxmoore14
  • 701
  • 7
  • 26
  • I agree that although there is a lot of documentation on the various Google apis it does feel a bit piecemeal and can be hard to determine all of the appropriate steps to make an oauth2 authentication and a request end to end. Let me know if you have any further luck. – Philip Rich Mar 29 '14 at 19:40
  • Hi, I'm not an expert of this API (I actually never used it), but I'm definitely your man for the .NET client library for Google APIs :) Regrading the OAuth 2.0 protocol - I recommend you reading the following page - https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth. Can you elaborate more on the signature process? Meantime I'll contact the owners of the Google Play Android Developer API. You should also take a look in this page: https://developers.google.com/android-publisher/getting_started – peleyal Mar 29 '14 at 20:13
  • Thanks for getting back to me. I *think* I've made a little progress with the client library. One suggestion for the install process - Google.Apis.Auth should be set as a nuget dependency I think. Mostly I think the documentation for the android-publisher library needs a lot of help. Its seems to bounce around between Service Account and Web Account when it would be much more useful to have it follow each option from start to finish and give use cases for both. In my case, I think I figured out that I need to use Service Account. – maxmoore14 Mar 30 '14 at 17:37
  • I haven't tested much yet, but assuming I can now call Get / Cancel to the Purchase Status API with the client library, that takes care of #3 above. I can't find any documentation on the format of the verification signature returned by the Android store though. Is it simply the public license key and I can do basic string comparison? Or is it some type of hash of the order data using the public key? My plan is to somehow verify the signature first, then check the status with the client library and if both are good, deliver the product. Sound about right? – maxmoore14 Mar 30 '14 at 17:45
  • @peleyal The code I am using from https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#service_account seems to be working on my dev machine locally, but not on Windows Azure because the line `var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);` throws an error. After some research, I found on Azure, I have to use `X509KeyStorageFlags.MachineKeySet`. But then I get the following error: – maxmoore14 Apr 01 '14 at 19:25
  • System.Security.Cryptography.CryptographicException: Key not valid for use in specified state. at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) at System.Security.Cryptography.Utils.ExportCspBlob(SafeKeyHandle hKey, Int32 blobType, ObjectHandleOnStack retBlob) at System.Security.Cryptography.Utils.ExportCspBlobHelper(Boolean includePrivateParameters, CspParameters parameters, SafeKeyHandle safeKeyHandle) at Google.Apis.Auth.OAuth2.ServiceAccountCredential.Initializer.FromCertificate(X509Certificate2 certificate) – maxmoore14 Apr 01 '14 at 19:26
  • Is there a way to create a new `ServiceAccountCredential` without calling `.FromCertificate()`? – maxmoore14 Apr 01 '14 at 19:28
  • Maybe the following thread will help you: http://stackoverflow.com/questions/18959418/site-in-azure-websites-fails-processing-of-x509certificate2? – peleyal Apr 01 '14 at 22:15
  • @peleyal Sorry for the delayed response, I was waiting on another component to be ready so I could live test this. The good news is that I am MUCH closer to solving this. Using your suggestions, I have successfully validated receipt signatures and created an `AndroidPublisherService`. When I call: `result = service.Purchases.Get(packageName, subscriptionID, token).Execute` I get a 403 error response from Google. I have checked my Google console and it is in fact getting my requests. – maxmoore14 May 06 '14 at 22:01
  • The response is: "The service androidpublisher has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError Forbidden [403] Errors [ Message[Forbidden] Location[ - ] Reason[forbidden] Domain[global] ]" – maxmoore14 May 06 '14 at 22:05
  • Maybe you don't use the right scopes? – peleyal May 07 '14 at 15:58
  • @peleyal Is it possible that there is something I didn't configure correctly through the Google Dev Console? Do I need to white list my domain for example? How can I get a more detailed error message? – maxmoore14 May 07 '14 at 20:03
  • You need to whitelist your domain if you use web-server oauth 2.0. As I see you use service account so you're using the p12 key, right? – peleyal May 07 '14 at 22:15
  • @peleyal That's correct. Can you either give me a quick walk through of how to whitelist or direct me towards an online doc that explains it? I've never used the Google console before, so I'm not sure where to do it. – maxmoore14 May 08 '14 at 02:29
  • @peleyal I updated the question with my actual code now. I think I have narrowed the problem, but need some help please. Thanks very much! – maxmoore14 May 08 '14 at 14:40
  • @peleyal Is this old blog post correct? http://milancermak.wordpress.com/2012/08/24/server-side-verification-of-google-play-subsc/ Can I not use a Service Account? – maxmoore14 May 08 '14 at 15:52

1 Answers1

1

This related post solved my 403 error issue: https://stackoverflow.com/a/23029201/2092250

It is working now. As far as the ConsoleApplicationName - I'm not even sure it is a required field. Might be completely ignored. That value doesn't seem to matter.

I must say, Google could not have possibly made this more confusing or more poorly documented. But it is functional finally!

Community
  • 1
  • 1
maxmoore14
  • 701
  • 7
  • 26