8.1 C
New York
Saturday, March 15, 2025

ios – Unable to Decode Apple In-App Buy Server-to-Server Notification Payload


I am implementing Apple In-App Buy server-to-server notifications in Laravel. I’ve arrange the mandatory credentials (issuer_id, key_id, and p8 personal key) from App Retailer Join and configured them in my utility. Nevertheless, I am unable to decode the signedPayload acquired from Apple’s notifications.

Right here’s my implementation:

class ServerNotificationAppleController extends Controller
{
    personal $storeKitKeysUrl="https://appleid.apple.com/auth/keys";

    public perform handleNotification(Request $request)
    {
        Log::information('Apple Notification Request:', $request->all());

        $signedPayload = $request->enter('signedPayload');

        if (!$signedPayload) {
            return response()->json(['error' => 'signedPayload not provided'], 400);
        }

        $jwtToken = $this->generateAppleJWT();

        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $jwtToken,
        ])->get($this->storeKitKeysUrl);

        Log::information('Apple Keys Standing:', ['status' => $response->status()]);
        Log::information('Apple Keys Physique:', ['body' => $response->body()]);

        if ($response->standing() !== 200) {
            return response()->json(['error' => "Apple public keys couldn't be retrieved"], 401);
        }

        $keysData = $response->json();

        $validatedPayload = $this->validateSignedPayload($signedPayload, $keysData);

        if (!$validatedPayload) {
            return response()->json(['error' => 'Invalid signedPayload'], 400);
        }

        Log::information("Apple Buy Knowledge:", (array)$validatedPayload);

        return response()->json(['message' => 'Notification processed successfully'], 200);
    }

    personal perform generateAppleJWT()
    {
        $keyId = config('providers.apple.key_id');
        $issuerId = config('providers.apple.issuer_id');
        $privateKey = file_get_contents(storage_path(config('providers.apple.private_key')));

        $nowUtc = Carbon::now();
        $expirationUtc = $nowUtc->copy()->addMinutes(20);

        $payload = [
            'iss' => $issuerId,
            'iat' => $nowUtc->timestamp,
            'exp' => $expirationUtc->timestamp,
            'aud' => 'appstoreconnect-v1',
        ];

        $header = [
            'kid' => $keyId,
            'alg' => 'ES256',
            'typ' => 'JWT'
        ];

        return JWT::encode($payload, $privateKey, 'ES256', $keyId, $header);
    }

    personal perform validateSignedPayload($signedPayload, $keysData)
    {
        attempt {
            $jwkKeys = JWK::parseKeySet($keysData);
            $allowedAlgs = new stdClass();
            $allowedAlgs->algos = ['ES256']; // Utilizing ES256
            return JWT::decode($signedPayload, $jwkKeys, $allowedAlgs);
        } catch (Exception $e) {
            Log::error("Apple Buy Validation Error: " . $e->getMessage() . " Hint: " . $e->getTraceAsString());
            return null;
        }
    }
}

The issue:

signedPayload is acquired accurately.
Fetching Apple’s public keys works nice (standing 200).
validateSignedPayload() fails to decode the payload and logs an error.

Questions:

  • Am I accurately fetching and utilizing Apple’s public keys for validation?
  • Do I must extract a particular a part of signedPayload earlier than decoding?
  • Might the difficulty be associated to how I am parsing the JWK keys?

Any insights or corrections could be appreciated!

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles