最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Unable to Decode Apple In-App Purchase Server-to-Server Notification Payload - Stack Overflow

programmeradmin6浏览0评论

I'm implementing Apple In-App Purchase server-to-server notifications in Laravel. I have set up the necessary credentials (issuer_id, key_id, and p8 private key) from App Store Connect and configured them in my application. However, I'm unable to decode the signedPayload received from Apple's notifications.

Here’s my implementation:

class ServerNotificationAppleController extends Controller
{
    private $storeKitKeysUrl = '';

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

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

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

        $jwtToken = $this->generateAppleJWT();

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

        Log::info('Apple Keys Status:', ['status' => $response->status()]);
        Log::info('Apple Keys Body:', ['body' => $response->body()]);

        if ($response->status() !== 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::info("Apple Purchase Data:", (array)$validatedPayload);

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

    private function generateAppleJWT()
    {
        $keyId = config('services.apple.key_id');
        $issuerId = config('services.apple.issuer_id');
        $privateKey = file_get_contents(storage_path(config('services.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);
    }

    private function validateSignedPayload($signedPayload, $keysData)
    {
        try {
            $jwkKeys = JWK::parseKeySet($keysData);
            $allowedAlgs = new \stdClass();
            $allowedAlgs->algos = ['ES256']; // Using ES256
            return JWT::decode($signedPayload, $jwkKeys, $allowedAlgs);
        } catch (\Exception $e) {
            Log::error("Apple Purchase Validation Error: " . $e->getMessage() . " Trace: " . $e->getTraceAsString());
            return null;
        }
    }
}

The problem:

signedPayload is received correctly. Fetching Apple's public keys works fine (status 200). validateSignedPayload() fails to decode the payload and logs an error.

Questions:

  • Am I correctly fetching and using Apple's public keys for validation?
  • Do I need to extract a specific part of signedPayload before decoding?
  • Could the issue be related to how I'm parsing the JWK keys?

Any insights or corrections would be appreciated!

发布评论

评论列表(0)

  1. 暂无评论