Post

Replies

Boosts

Views

Activity

Reply to Internal testing. Receipt not always contain last consumable purchase.
I messed the code in previous post: using Jose; using Newtonsoft.Json; using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; private static ECDsa LoadApplePrivateKey(string filePath) { var keyText = File.ReadAllText(filePath) .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") .Replace("\r", "") .Replace("\n", "") .Trim(); var keyBytes = Convert.FromBase64String(keyText); var ecdsa = ECDsa.Create(); ecdsa.ImportPkcs8PrivateKey(keyBytes, out _); return ecdsa; } private string GenerateApiToken() { var payload = new Dictionary<string, object> { { "iss", _issuerId }, { "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() }, { "exp", DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeSeconds() }, { "aud", "appstoreconnect-v1" }, { "bid", _bundleId } }; var extraHeaders = new Dictionary<string, object> { { "alg", "ES256" }, { "kid", _keyId }, { "typ", "JWT" } }; string token = JWT.Encode(payload, _privateKey, JwsAlgorithm.ES256, extraHeaders); _logger.LogInformation("Generated JWT: {Token}", token); return token; } public static ApplePurchase DecodeJws(string jws) { var parts = jws.Split('.'); if (parts.Length != 3) throw new ArgumentException("Invalid JWS format"); var payloadJson = Encoding.UTF8.GetString(Base64Url.Decode(parts[1])); dynamic payload = JsonConvert.DeserializeObject(payloadJson); return new ApplePurchase { transactionId = payload.transactionId, productId = payload.productId, timePurchased = payload.purchaseDate, environment = payload.environment ?? "Unknown" }; }
Sep ’25
Reply to Internal testing. Receipt not always contain last consumable purchase.
using Jose; using Newtonsoft.Json; using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; This Generation of Api token works on .net 9 I post the main methods I struggle with: { var keyText = File.ReadAllText(filePath) .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") .Replace("\r", "") .Replace("\n", "") .Trim(); var keyBytes = Convert.FromBase64String(keyText); var ecdsa = ECDsa.Create(); ecdsa.ImportPkcs8PrivateKey(keyBytes, out _); return ecdsa; } private string GenerateApiToken() { var payload = new Dictionary<string, object> { { "iss", _issuerId }, { "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() }, { "exp", DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeSeconds() }, { "aud", "appstoreconnect-v1" }, { "bid", _bundleId } }; var extraHeaders = new Dictionary<string, object> { { "alg", "ES256" }, { "kid", _keyId }, { "typ", "JWT" } }; string token = JWT.Encode(payload, _privateKey, JwsAlgorithm.ES256, extraHeaders); _logger.LogInformation("Generated JWT: {Token}", token); return token; } public static ApplePurchase DecodeJws(string jws) { var parts = jws.Split('.'); if (parts.Length != 3) throw new ArgumentException("Invalid JWS format"); var payloadJson = Encoding.UTF8.GetString(Base64Url.Decode(parts[1])); dynamic payload = JsonConvert.DeserializeObject(payloadJson); return new ApplePurchase { transactionId = payload.transactionId, productId = payload.productId, timePurchased = payload.purchaseDate, environment = payload.environment ?? "Unknown" }; }```
Sep ’25
Reply to Internal testing. Receipt not always contain last consumable purchase.
Now I try to use JWT. But I receive JWT is not well formed. I use .NET 9 my code is private string GenerateApiToken() { var now = DateTimeOffset.UtcNow; var expiry = now.AddMinutes(5); var credentials = new SigningCredentials( new ECDsaSecurityKey(_privateKey) { KeyId = _keyId }, SecurityAlgorithms.EcdsaSha256); var claims = new Dictionary<string, object> { { "bid", _bundleId } // Apple requires this }; var descriptor = new SecurityTokenDescriptor { Issuer = _issuerId, IssuedAt = now.UtcDateTime, Expires = expiry.UtcDateTime, Audience = "appstoreconnect-v1", Claims = claims, //NotBefore = now.UtcDateTime, SigningCredentials = credentials }; var handler = new JwtSecurityTokenHandler(); var token = handler.CreateJwtSecurityToken(descriptor); // Force Apple-required headers token.Header["alg"] = "ES256"; token.Header["kid"] = _keyId; token.Header["typ"] = "JWT"; var jwt = handler.WriteToken(token); Console.WriteLine($"Generated JWT: {jwt}"); return jwt; }
Sep ’25
Reply to Internal testing. Receipt not always contain last consumable purchase.
//My validator public static async Task<AppleValidationResult> ValidateReceiptAsync(string base64Receipt) { var requestJson = new JObject { ["receipt-data"] = base64Receipt, ["password"] = SharedSecret }; var content = new StringContent(requestJson.ToString(), Encoding.UTF8, "application/json"); // First try production var response = await client.PostAsync(ProductionUrl, content); var json = JObject.Parse(await response.Content.ReadAsStringAsync()); int status = (int) json["status"]; if (status == 21007) // Receipt from sandbox { response = await client.PostAsync(SandboxUrl, content); json = JObject.Parse(await response.Content.ReadAsStringAsync()); status = (int) json["status"]; Debug.Log("status=" + status); } Debug.Log("status="+status); if (status != 0) return new AppleValidationResult { Valid = false }; // Get the last in-app purchase var latestReceipt = json["receipt"]?["in_app"]?.Last; Debug.Log("latest receipt = " + latestReceipt!=null); if (latestReceipt == null) return new AppleValidationResult { Valid = false }; return new AppleValidationResult { Valid = true, TransactionId = latestReceipt["transaction_id"]?.ToString(), ProductId = latestReceipt["product_id"]?.ToString() }; }
Sep ’25