Search code examples
google-pay

Tokenizing Google Pay Data with CardPointe - Decryption failure


I'm working on implementing Google Pay in my website using the "cardconnect" gateway. I've got the Google Pay API implemented in javascript and that seems to be working fine. I've also implemented the code that decrypts the google pay payload data and tokenizes it for CardPointe. See the procedure below. The "data" variable is the google payload. I'm following the instructions here https://developer.cardpointe.com/guides/google-pay#support. Google has approved my website.

This works when Google Pay is in TEST mode but when I change it to PRODUCTION I get this response status InternalServerError reasonphrase {"message":"Decryption failure","errorcode":24}

public async Task<string> TokenizeGooglePayDataCardPointe(string data) {
    Admins adminBRs = new Admins(_context);
    string uri = adminBRs.GetPageSetting("Donations", "CardPointeURL");
    if (!uri.StartsWith("http")) uri = "https://" + uri;
    if (!uri.EndsWith("/")) uri += "/";
    string uri2 = uri + "cardsecure/api/v1/ccn/tokenize";

    dynamic obj = new System.Dynamic.ExpandoObject();
    obj.devicedata = data;
    obj.encryptionhandler = "EC_GOOGLE_PAY";
    string content = JsonConvert.SerializeObject(obj);

    var contentString = new StringContent(content, System.Text.Encoding.UTF8, "application/json");
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
    try {
        using (HttpClient client = new HttpClient()) {
            // Set request headers.
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = await client.PostAsync(uri2, contentString);
            if (response != null) {
                if (response.IsSuccessStatusCode) {
                    if (response.Content != null) {
                        string jsonString = response.Content.ReadAsStringAsync().Result;
                        JObject results = JsonConvert.DeserializeObject<JObject>(jsonString);
                        JToken errorCode = results.GetValue("errorcode");
                        JToken message = results.GetValue("message");
                        if (errorCode.ToString() == "0") {
                            JToken token = results.GetValue("token");
                            return token.ToString();
                        } else {
                            return "Error: " + errorCode.ToString() + " " + message.ToString();
                        }
                    } else {
                        // not sure if this can happen
                        LogInfo.LogText(_context, "Google Pay tokenization error: no reponse content");
                        return "Error: No content returned";
                    }
                } else {
                    if (response.StatusCode == HttpStatusCode.RequestTimeout) {
                        LogInfo.LogText(_context, "Google Pay tokenization error: timeout");
                        return "Error: timeout";
                    } else {
                        string jsonString = "";
                        if (response.Content != null) jsonString = response.Content.ReadAsStringAsync().Result;
                        LogInfo.LogText(_context, "Google Pay tokenization error: " + response.StatusCode.ToString() + " " + response.ReasonPhrase + " " + jsonString + " " + data);
                        return "Error: status " + response.StatusCode.ToString();
                    }
                }
            } else {
                return "Error: no response";
            }
        }
    } catch (Exception ex) {
        LogInfo.LogError(_context, ex);
        return "Error";
    }
}

In TEST mode the "data" payload that works looks like

{"signature":"MEYCIQCxG3oEWSF2NhoONkB6O7h3sbgHBQ9cQZDI9a1zkaL5PwIhAJY7g5ekerICbmzcD9ocvbsrSC7pup5zLUkBjQmE4jhD","intermediateSigningKey":{"signedKey":"{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaYdF4BdKTIsIKDczrxSvOOU6sF6DTxkJCv5RZX140mswhcpFwfxz2GlfFRcKyOFYBCn/tFk+fOBUVxrnIIchIQ\\u003d\\u003d\",\"keyExpiration\":\"1718470102231\"}","signatures":["MEQCIELE+SRM6YsF7hL2YHEq7lkN4iM13kPGG3Gxyf3ubJvhAiBJhPFJBtywGWJPWI0O465P7QUnSB7P0zl+wtQtVYE4fQ\u003d\u003d"]},"protocolVersion":"ECv2","signedMessage":"{\"encryptedMessage\":\"k67b8MkJ/xyd2xvzwQ4aJllYcBp8Zm6DRqXanyN113kEzzUhgwrxZCAidWaWJZhUQiFm/OfrwLP1rW/UzFoHULU0tO5GQDRAQDK40A00tTzeoKZGpKeXo4uaHXUcJvpbVHTVbqL5BwR3v9O4OvyRD7/X6J1pv60RzTsxpd0+sGEnNziDVGEB2lK4y1MPxWN3NF/6IbG5XidK5ZIVv8l5D9QafZoDyARYQnup7i7jDOX2yRO5H/NJYPr2r71Cu50jmHiIyl9LCLhazBqKtlxqd0APYbrjBqEeyuaaq8USSCPJTDEoZ1bljjMiyd4/xBJY5pOqH2KFyspsb3MYZK3zWkmOrk+ui85DC7G/Hf28ZvNZjy1eq7MVPhLFGGczARH2IP8cQRZN/wBbPutK2MbHZW7LFZQ/dWA90LJY11WbniX9FZSukuQurWIS95FpLD1k6bgoMkhK5P713w\\u003d\\u003d\",\"ephemeralPublicKey\":\"BC4OLFg8aE9uZk2H2mlzu1a+aavaq2ZtEqOpiNaGWFv170lm0RfeffIYquzYw3Ybkp5WOTa/NIaUik122BuFegM\\u003d\",\"tag\":\"RY65Wy6zyDSj9zIpGfvzlSgzClfsvNqFALvcqsGhzYo\\u003d\"}"}

In PRODUCTION mode the "data" looks like this

{"signature":"MEQCIAo9FwgaZ8/vYsV2HhTzx3r3YALGnguYp3qLJwMUYd/pAiBTdDDrWWq4WmLCkbQBWB71vvtPpdwUXw/zNv527WfylQ\u003d\u003d","intermediateSigningKey":{"signedKey":"{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqaR+yoImVfXIW+CRXutpeiGFmiC0FLtnKURmYF0LIbw3SEIQLe8sKwBu7Wa2VEjLTMVEMXnnSXDoy4EBfwOSTg\\u003d\\u003d\",\"keyExpiration\":\"1718469250000\"}","signatures":["MEUCIFSNA9M0hkBvdNmw7tWb9vb8vHeyoeWku2rwjtgh9j8OAiEA/cmTXhrqnAmr9inO9C6tsfjqrBpqvvIC2MVpRsaYI00\u003d"]},"protocolVersion":"ECv2","signedMessage":"{\"encryptedMessage\":\"XKUiZp1NtZAgEfJLx5IjdFAArvd4jXz62lF0ltmICvcdGk8F2T5zibO22tXFa7pZw6yfcDh43S+bVmVll9MpCdrg8Yk9T/nGIxqBwZoDLr9E53LJSs3s8mVz1skhWtOEd/Z89eE/GRml1qkqpiRpgYuohdGaC0Ea2EIE3dw8RIhHrevlwSFp1gRnmsL36SVboV7unyv95T/jQPVdwidm5rnjN2eZKOSAhPT71e5gNNMxuwZDD27ifq1WCpLPX02BIJzvpRHB6tGlB04/KPg758eqvBfL1VQuCasjDZ7LPlkO9eb3XFbLxMoxSnMOErXADvNOJhYZuaqJ9sKq7jpGbeUZjEoXd9Rw9C6EdRUhDQwb0ZTtkcoFVU+cgC+Xe2zD5KVQjaHgT9mX/zklHlosBX029LbOsbzhafLwn1lJH2PtGEbv3QkpVfRfXd/bYwv1mmV/NJ8cIo9K\",\"ephemeralPublicKey\":\"BK4KiIMRRVGwkKrbyHsFFzsFB09eyelyQKxp6Tqr3/68UXXpJ/I5Ub75O1BbvZvKwawndHSHaCguQaM83COAYxg\\u003d\",\"tag\":\"cWo2XNrVH98EVNdNLnZniWXKU2CAcImRKgHHdviQVwU\\u003d\"}"}

They both look pretty similar to me. Any thoughts? Maybe something to do with the way I'm setting obj.devicedata = data?


Solution

  • Turns out this code is correct. The problem was that the CardConnect gateway was in test mode while Google Pay was in production mode and that configuration is not supported