Showing
2 changed files
with
120 additions
and
4 deletions
| ... | @@ -13,16 +13,19 @@ use App\Models\Order; | ... | @@ -13,16 +13,19 @@ use App\Models\Order; |
| 13 | use App\Models\User; | 13 | use App\Models\User; |
| 14 | use App\Models\UserProfile; | 14 | use App\Models\UserProfile; |
| 15 | use Carbon\Carbon; | 15 | use Carbon\Carbon; |
| 16 | +use Firebase\JWT\SignatureInvalidException; | ||
| 16 | use GuzzleHttp\Client; | 17 | use GuzzleHttp\Client; |
| 17 | use Illuminate\Support\Facades\Log; | 18 | use Illuminate\Support\Facades\Log; |
| 18 | -use Illuminate\Support\Facades\Redis; | 19 | +use Firebase\JWT\JWT; |
| 20 | +use Firebase\JWT\Key; | ||
| 21 | +use Illuminate\Support\Facades\File; | ||
| 19 | 22 | ||
| 20 | class ApplePayment implements PaymentInterface | 23 | class ApplePayment implements PaymentInterface |
| 21 | { | 24 | { |
| 22 | 25 | ||
| 23 | const IS_SANDBOX = true; | 26 | const IS_SANDBOX = true; |
| 24 | 27 | ||
| 25 | - | 28 | + const CA_PATH = "/AppleRootCA-G3.pem"; |
| 26 | 29 | ||
| 27 | public function __construct() | 30 | public function __construct() |
| 28 | { | 31 | { |
| ... | @@ -48,7 +51,6 @@ class ApplePayment implements PaymentInterface | ... | @@ -48,7 +51,6 @@ class ApplePayment implements PaymentInterface |
| 48 | public function notifySandbox($string) | 51 | public function notifySandbox($string) |
| 49 | { | 52 | { |
| 50 | Log::debug('sandbox返回的数据:===================='); | 53 | Log::debug('sandbox返回的数据:===================='); |
| 51 | - Log::debug($string); | ||
| 52 | 54 | ||
| 53 | $components = explode('.',$string); | 55 | $components = explode('.',$string); |
| 54 | if (count($components) < 3){ | 56 | if (count($components) < 3){ |
| ... | @@ -58,6 +60,105 @@ class ApplePayment implements PaymentInterface | ... | @@ -58,6 +60,105 @@ class ApplePayment implements PaymentInterface |
| 58 | 60 | ||
| 59 | $header = json_decode(base64_decode($components[0]),true); | 61 | $header = json_decode(base64_decode($components[0]),true); |
| 60 | 62 | ||
| 61 | - Log::debug(print_r($header,true)); | 63 | + // 这一步可以省略,不需要验证根证书 |
| 64 | + $this->validateAppleRootCa($header); | ||
| 65 | + $responseBodyPayload = $this->decodeCertificate($string, $header['x5c'][0]); | ||
| 66 | + Log::debug(print_r($responseBodyPayload,true)); | ||
| 67 | + /**{ | ||
| 68 | + "notificationType": "SUBSCRIBED" | ||
| 69 | + "subtype": "RESUBSCRIBE" | ||
| 70 | + "notificationUUID": "99e65e59-c178-4f49-8b83-ea7d916cb568" | ||
| 71 | + "data": { | ||
| 72 | + "bundleId": "ink.parlando.parlando" | ||
| 73 | + "bundleVersion": "13" | ||
| 74 | + "environment": "Sandbox" | ||
| 75 | + "signedTransactionInfo": "xxx" | ||
| 76 | + "signedRenewalInfo": "xxx" | ||
| 77 | + } | ||
| 78 | + "version": "2.0" | ||
| 79 | + "signedDate": 1671451705697 | ||
| 80 | + } | ||
| 81 | + */ | ||
| 82 | + | ||
| 83 | + $signedTransactionInfoString = $responseBodyPayload['data']['signedTransactionInfo']; | ||
| 84 | + $components = explode('.',$signedTransactionInfoString); | ||
| 85 | + $header = json_decode(base64_decode($components[0]),true); | ||
| 86 | + if (count($components) < 3){ | ||
| 87 | + Log::error("jwt错误"); | ||
| 88 | + return false; | ||
| 89 | + } | ||
| 90 | + $signedTransactionInfo = $this->decodeCertificate($string, $header['x5c'][0]); | ||
| 91 | + Log::debug(print_r($signedTransactionInfo,true)); | ||
| 92 | + /**{ | ||
| 93 | + "transactionId": "2000000231419425" | ||
| 94 | + "originalTransactionId": "2000000229164150" | ||
| 95 | + "webOrderLineItemId": "2000000017115109" | ||
| 96 | + "bundleId": "ink.parlando.parlando" | ||
| 97 | + "productId": "monthly_yiyan_vip" | ||
| 98 | + "subscriptionGroupIdentifier": "21080623" | ||
| 99 | + "purchaseDate": 1671451694000 | ||
| 100 | + "originalPurchaseDate": 1671123372000 | ||
| 101 | + "expiresDate": 1671451994000 | ||
| 102 | + "quantity": 1 | ||
| 103 | + "type": "Auto-Renewable Subscription" | ||
| 104 | + "inAppOwnershipType": "PURCHASED" | ||
| 105 | + "signedDate": 1671451705700 | ||
| 106 | + "environment": "Sandbox" | ||
| 107 | + }*/ | ||
| 108 | + $signedRenewalInfoString = $responseBodyPayload['data']['signedRenewalInfo']; | ||
| 109 | + $components = explode('.',$signedRenewalInfoString); | ||
| 110 | + $header = json_decode(base64_decode($components[0]),true); | ||
| 111 | + if (count($components) < 3){ | ||
| 112 | + Log::error("jwt错误"); | ||
| 113 | + return false; | ||
| 114 | + } | ||
| 115 | + $signedRenewalInfo = $this->decodeCertificate($string, $header['x5c'][0]); | ||
| 116 | + Log::debug(print_r($signedRenewalInfo,true)); | ||
| 117 | + /**{ | ||
| 118 | + "originalTransactionId": "2000000229164150" | ||
| 119 | + "autoRenewProductId": "monthly_yiyan_vip" | ||
| 120 | + "productId": "monthly_yiyan_vip" | ||
| 121 | + "autoRenewStatus": 1 | ||
| 122 | + "signedDate": 1671451705673 | ||
| 123 | + "environment": "Sandbox" | ||
| 124 | + "recentSubscriptionStartDate": 1671451694000 | ||
| 125 | + }*/ | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + private function validateAppleRootCa($header) | ||
| 129 | + { | ||
| 130 | + $lastIndex = count($header['x5c']) - 1; | ||
| 131 | + | ||
| 132 | + $certificate = $this->getCertificate($header['x5c'][$lastIndex]); | ||
| 133 | + | ||
| 134 | + if ($certificate != File::get(public_path(self::CA_PATH))) return false; | ||
| 135 | + | ||
| 136 | + return true; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + private function getCertificate($string) | ||
| 140 | + { | ||
| 141 | + $certificate = "-----BEGIN CERTIFICATE-----" . PHP_EOL; | ||
| 142 | + $certificate .= chunk_split($string, 64, PHP_EOL); | ||
| 143 | + $certificate .= "-----END CERTIFICATE-----" . PHP_EOL; | ||
| 144 | + return $certificate; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + private function decodeCertificate($string, $appleCertificate) | ||
| 148 | + { | ||
| 149 | + $certificate = $this->getCertificate($appleCertificate); | ||
| 150 | + | ||
| 151 | + $cert_object = openssl_x509_read($certificate); | ||
| 152 | + $pkey_object = openssl_pkey_get_public($cert_object); | ||
| 153 | + $pkey_array = openssl_pkey_get_details($pkey_object); | ||
| 154 | + $public_key = $pkey_array["key"]; | ||
| 155 | + | ||
| 156 | + try{ | ||
| 157 | + $decode = JWT::decode($string, new Key($public_key, "ES256")); | ||
| 158 | + return (array)$decode; | ||
| 159 | + }catch (SignatureInvalidException $exception){ | ||
| 160 | + Log::error("Signature Invalid!"); | ||
| 161 | + return false; | ||
| 162 | + } | ||
| 62 | } | 163 | } |
| 63 | } | 164 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
public/AppleRootCA-G3.pem
0 → 100644
| 1 | +-----BEGIN CERTIFICATE----- | ||
| 2 | +MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS | ||
| 3 | +QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u | ||
| 4 | +IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN | ||
| 5 | +MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS | ||
| 6 | +b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y | ||
| 7 | +aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49 | ||
| 8 | +AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf | ||
| 9 | +TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517 | ||
| 10 | +IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr | ||
| 11 | +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA | ||
| 12 | +MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4 | ||
| 13 | +at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM | ||
| 14 | +6BgD56KyKA== | ||
| 15 | +-----END CERTIFICATE----- |
-
Please register or login to post a comment