App Store 서버 알림에 대한 서버 URL 입력 - 앱 내 구입 설정 진행 - App Store Connect - 도움말 - Apple Devel
앱 내 구입 설정 진행 App Store 서버 알림에 대한 서버 URL 입력 “App Store 서버 알림”은 구독 상태 변경 또는 앱 내 구입의 환불과 같이 앱 내 구입과 관련된 주요 이벤트의 정보를 제공합니다. App
developer.apple.com
애플 앱스토어에 앱등록을 진행하다보면 App Store 서버 알림이라는 부분을 등록해야 할때가 있다
이는 애플 서버에서 사용자의 구독 , 구독 취소, 환불 요청 시작, 환불 등 중요한 정보에 대한 알림을 받을 서버 주소를 입력해 주어야 한다.
위 의 페이지에서 변경 할 수 있고 변경을 누를 경우 버전 1과 버전2중 선택을 하여 등록할수 있다.
지금은 버전1에 대한 설명을 하려고 한다.
서버에서 알림은 JSON 형태로 오게 된다 그래서 file_get_contents( 'php://input' )으로 데이터를 받아 가공 처리하면된다.
Codeigniter 에서는 $this->input->raw_input_stream 으로 받아 처리하면된다.
function callback()
{
$callBackData = json_decode($this->input->raw_input_stream, true);
if ($callBackData['notification_type'] == 'CANCEL')
{
// Apple 지원이 자동 갱신 구독을 취소했고 고객이 의 타임스탬프를 기준으로 환불을 받았음을 나타냅니다
}
else if ($callBackData['notification_type'] == 'DID_CHANGE_RENEWAL_PREF')
{
// 고객이 다음 갱신 시 적용되는 구독 요금제를 변경했음을 나타냅니다. 현재 활성 계획은 영향을 받지 않습니다. 고객의 구독이 갱신되는 제품의 제품 식별자를 검색하려면 필드를 확인하십시오 .auto_renew_product_idunified_receipt.Pending_renewal_info
}
else if ($callBackData['notification_type'] == 'DID_CHANGE_RENEWAL_STATUS')
{
// 구독 갱신 상태의 변경을 나타냅니다. JSON 응답에서 마지막 상태 업데이트 날짜 및 시간을 검색하도록 확인합니다. 현재 갱신 상태를 확인 하려면 선택하십시오.auto_renew_status_change_date_msauto_renew_status
}
else if ($callBackData['notification_type'] == 'DID_FAIL_TO_RENEW')
{
// 청구 문제로 인해 갱신에 실패한 구독을 나타냅니다. 구독의 현재 재시도 상태를 검색하려면 선택하십시오 . 구독이 청구 유예 기간인 경우 새 서비스 만료 날짜를 확인 하려면 선택하십시오.is_in_billing_retry_periodgrace_period_expires_date
}
else if ($callBackData['notification_type'] == 'DID_RECOVER')
{
// 과거에 갱신에 실패한 만료된 구독의 성공적인 자동 갱신을 나타냅니다. 다음 갱신 날짜 및 시간을 확인하려면 확인하십시오 .expires_date
}
else if ($callBackData['notification_type'] == 'DID_RENEW')
{
// 고객의 구독이 새로운 거래 기간 동안 성공적으로 자동 갱신되었음을 나타냅니다. 고객에게 구독 콘텐츠 또는 서비스에 대한 액세스 권한을 제공합니다.
}
else if ($callBackData['notification_type'] == 'INITIAL_BUY')
{
// 사용자가 구독을 처음 구매할 때 발생합니다. App Store에서 유효성을 검사하여 언제든지 사용자의 구독 상태를 확인할 수 있도록 서버에 토큰으로 저장하십시오 .latest_receipt
}
else if ($callBackData['notification_type'] == 'INTERACTIVE_RENEWAL')
{
// 고객이 앱의 인터페이스를 사용하거나 계정의 구독 설정에 있는 App Store에서 대화식으로 구독을 갱신했음을 나타냅니다. 즉시 서비스를 제공하십시오.
}
else if ($callBackData['notification_type'] == 'PRICE_INCREASE_CONSENT')
{
// App Store에서 동의가 필요한 앱의 자동 갱신 구독 가격 인상에 대한 동의를 고객에게 요청하기 시작했음을 나타냅니다.
// 개체 에서 값은 사용자가 아직 가격 인상에 응답하지 않았음을 나타냅니다.unified_receipt.Pending_renewal_infoprice_consent_status0
// App Store 서버는 고객이 가격 인상에 동의하는 시점 을 로 설정합니다.price_consent_status1
// App Store Server API에서 Get All Subscription Statuses 엔드포인트를 호출하여 최신 가격 동의 상태를 확인하십시오 . 에서 필드를 확인하십시오 . 또한 verifyReceipt를 호출하여 업데이트된 가격 동의 상태를 볼 수 있습니다.priceIncreaseStatusJWSRenewalInfoDecodedPayload
// 고객 동의가 필요한 구독 가격 인상에 대한 가격 동의 시트를 표시하기 전에 StoreKit이 앱을 호출하는 방법에 대한 자세한 내용은 을 참조하십시오 . 구독 가격 관리에 대한 자세한 내용은 가격 관리를 참조하세요.paymentQueueShouldShowPriceConsent(_:)
}
else if ($callBackData['notification_type'] == 'REVOKE')
{
// 가족 공유를 통해 사용자에게 부여된 인앱 구매를 공유를 통해 더 이상 사용할 수 없음을 나타냅니다. StoreKit은 구매자가 제품에 대해 가족 공유를 비활성화했거나 구매자(또는 가족 구성원)가 가족 그룹을 떠났거나 구매자가 환불을 요청하고 받았을 때 이 알림을 보냅니다. 앱에서도 전화를 받습니다 . 가족 공유에 대한 자세한 내용은 앱에서 가족 공유 지원을 참조하십시오 .paymentQueue(_:didRevokeEntitlementsForProductIdentifiers:)
}
else if ($callBackData['notification_type'] == 'CONSUMPTION_REQUEST')
{
// 고객이 소모성 인앱 구매에 대한 환불 요청을 시작했으며 App Store에서 소비 데이터 제공을 요청하고 있음을 나타냅니다. 자세한 내용은 소비 정보 보내기를 참조하십시오 .
// transaction_id 에 해당하는 사용자의 아이디를 찾아 환불 사전정보를 전달해준다.
$this->sendAppleConsumption($callBackData['original_transaction_id']);
}
else if ($callBackData['notification_type'] == 'REFUND')
{
// App Store에서 소모성 인앱 구매, 비소모성 인앱 구매 또는 비갱신 구독에 대한 거래를 성공적으로 환불했음을 나타냅니다. 에는 환불된 거래의 타임스탬프가 포함되어 있습니다. 및 원래 거래 및 제품을 식별합니다 . 에는 이유가 포함되어 있습니다.cancellation_date_msoriginal_transaction_idproduct_idcancellation_reason
$aReceipt = $callBackData['unified_receipt']['latest_receipt_info'];
for ($i = 0; $i < count($aReceipt); $i++)
{
// $aReceipt[$i]['original_transaction_id']
// transaction_id 로 결제건을 찾아 환불처리해준다.
}
}
$this->output->set_output("<html><body><RESULT>SUCCESS</RESULT></body></html>");
}
위 처럼 코드를 구성할수 있다.
자세한 내용은 https://developer.apple.com/documentation/appstoreservernotifications/notification_type 참조.
여기서 소모성 아이템에 대한 환불 요청과 환불에 대한 처리를 했다.
앱스토어에서 환불 요청시 소비 데이터 요청을 하는 경우에 대한 응답을 해보도록 하겠다.
function sendAppleConsumption($payment_transaction_id)
{
// transaction_id 로 결제 정보와 사용자의 정보를 불러온다.
$buldle_id = "";// 앱번들아이디.
$msg = array();
$msg['accountTenure'] = 0; // 0~7 (필수) 고객 계정의 연령입니다.
$msg['appAccountToken'] = $this->getUuidV4(sprintf("%016d", '회원번호')); // (필수) 소모성 인앱 구매 거래를 완료한 인앱 사용자 계정의 UUID입니다.
$msg['consumptionStatus'] = 0; // 0~3 (필수) 고객이 인앱 구매를 한 정도를 나타내는 값.
$msg['customerConsented'] = true; // (필수) 고객이 소비 데이터 제공에 동의했는지 여부를 나타내는 true또는 의 부울 값 입니다.false
$msg['deliveryStatus'] = 5; // 0~5 // (필수) 앱이 제대로 작동하는 인앱 구매를 성공적으로 전달했는지 여부를 나타내는 값입니다.
$msg['lifetimeDollarsPurchased'] = 0; // 0~7 (필수) 모든 플랫폼에서 고객이 앱에서 수행한 총 인앱 구매 금액(USD)을 나타내는 값입니다.
$msg['lifetimeDollarsRefunded'] = 0; // 0~7 (필수) 앱에서 모든 플랫폼에 걸쳐 고객이 받은 환불의 총액을 USD로 나타내는 값입니다.
$msg['platform'] = 1; // 0~2 (필수) 고객이 인앱 구매를 소비한 플랫폼을 나타내는 값입니다.
// 0 선언되지 않음. 1 애플 플랫폼. 2 비 Apple 플랫폼.
$msg['playTime'] = 0; // 0~7 (필수) 고객이 앱을 사용한 시간을 나타내는 값입니다.
$msg['sampleContentProvided'] = true; // (필수) 구매 전에 콘텐츠의 무료 샘플 또는 평가판을 제공했는지 또는 해당 기능에 대한 정보를 제공했는지 여부를 나타내는 true또는 의 부울 값 입니다.false
$msg['userStatus'] = 0; // 0~4 (필수) 고객 계정의 상태입니다.
if(결제정보가 서버에 저장되었고 사용자 정보가 있는경우)
{
// accountTenure
// 0 계정 연령이 선언되지 않았습니다.
// 1 계정 기간은 0~3일입니다.
// 2 계정 기간은 3~10일입니다.
// 3 계정 기간은 10~30일입니다.
// 4 계정 기간은 30~90일입니다.
// 5 계정 기간은 90~180일입니다.
// 6 계정 기간은 180~365일입니다.
// 7 계정 사용 기간이 365일을 초과했습니다.
$day = floor((abs(time() - strtotime('사용자가입일')) / 60 / 60 / 24));
if ($day <= 3)
{
$msg['accountTenure'] = 1;
}
else if ($day <= 10)
{
$msg['accountTenure'] = 2;
}
else if ($day <= 30)
{
$msg['accountTenure'] = 3;
}
else if ($day <= 90)
{
$msg['accountTenure'] = 4;
}
else if ($day <= 180)
{
$msg['accountTenure'] = 5;
}
else if ($day <= 365)
{
$msg['accountTenure'] = 6;
}
else
{
$msg['accountTenure'] = 7;
}
// consumptionStatus
// 0 소비 상태가 선언되지 않았습니다.
// 1 인앱 구매는 소비되지 않습니다.
// 2 인앱 구매가 부분적으로 소비됩니다.
// 3 인앱 구매가 완전히 소비되었습니다.
if (충전된 잔여 금액이 환불될 금액 보다 많아 환불가능한경우 cash >= 상품가격)
{
$msg['consumptionStatus'] = 1;
}
else if (충전 잔여 금액이 적어 환불될 금액보다 적은경우 cash < 상품가격)
{
$msg['consumptionStatus'] = 2;
}
else
{
$msg['consumptionStatus'] = 3;
}
// deliveryStatus
// 0 앱에서 소모품 인앱 구매를 전달했으며 제대로 작동합니다.
// 1 품질 문제로 인해 앱에서 소모품 인앱 구매를 제공하지 않았습니다.
// 2 앱에서 잘못된 항목을 배송했습니다.
// 3 서버 중단으로 인해 앱에서 소모성 인앱 구매를 제공하지 않았습니다.
// 4 게임 내 통화 변경으로 인해 앱에서 소모품 인앱 구매를 제공하지 않았습니다.
// 5 앱에서 다른 이유로 소모품 인앱 구매를 제공하지 않았습니다.
$msg['deliveryStatus'] = 0;
// lifetimeDollarsPurchased
// 0 평생 구매 금액은 미신고입니다.
// 1 평생 구매 금액은 0 USD입니다.
// 2 평생 구매 금액은 0.01–49.99 USD입니다.
// 3 평생 구매 금액은 50–99.99 USD입니다.
// 4 평생 구매 금액은 100–499.99 USD입니다.
// 5 평생 구매 금액은 500–999.99 USD입니다.
// 6 평생 구매 금액은 1000–1999.99 USD입니다.
// 7 평생 구매 금액은 2000 USD 이상입니다.
$pay = 총 결제 금액 - 현재 환불 대상 결제 금액
if ($pay <= 0)
{
$msg['lifetimeDollarsPurchased'] = 1;
}
else if ($pay <= 55000)
{
$msg['lifetimeDollarsPurchased'] = 2;
}
else if ($pay <= 110000)
{
$msg['lifetimeDollarsPurchased'] = 3;
}
else if ($pay <= 550000)
{
$msg['lifetimeDollarsPurchased'] = 4;
}
else if ($pay <= 1100000)
{
$msg['lifetimeDollarsPurchased'] = 5;
}
else if ($pay <= 2200000)
{
$msg['lifetimeDollarsPurchased'] = 6;
}
else
{
$msg['lifetimeDollarsPurchased'] = 7;
}
// lifetimeDollarsRefunded
// 0 평생 환불 금액은 미신고입니다.
// 1 평생 환불 금액은 0 USD입니다.
// 2 평생 환불 금액은 0.01–49.99 USD입니다.
// 3 평생 환불 금액은 50–99.99 USD입니다.
// 4 평생 환불 금액은 100–499.99 USD입니다.
// 5 평생 환불 금액은 500–999.99 USD입니다.
// 6 평생 환불 금액은 1000–1999.99 USD입니다.
// 7 평생 환불 금액은 2000 USD 이상입니다.
// playTime
// 0 참여 시간은 선언되지 않았습니다.
// 1 참여 시간은 0~5분입니다.
// 2 참여 시간은 5~60분입니다.
// 3 참여 시간은 1~6시간입니다.
// 4 참여 시간은 6~24시간입니다.
// 5 참여 시간은 1~4일입니다.
// 6 참여 시간은 4~16일입니다.
// 7 참여 기간은 16일이 넘습니다.
// userStatus
// 0 계정 상태가 선언되지 않았습니다.
// 1 고객의 계정이 활성 상태입니다.
// 2 고객의 계정이 정지되었습니다.
// 3 고객의 계정이 해지됩니다.
// 4 고객의 계정은 액세스가 제한되어 있습니다.
}
return $this->sendConsumption($buldle_id, $payment_transaction_id, json_encode($msg));
}
위에서 appAccountToken 을 제대로 보내지 않으면 서버에서 제대로된 응답을 내려주지 않는다.
appAccountToken 의 값은 애플 UUID 형식에 맞게 전달해야 한다.
해당 코드는 아래처럼 하면된다.
function getUuidV4($data = null)
{
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = $data ?? random_bytes(16);
assert(strlen($data) == 16);
// Set version to 0100
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
32개의 문자와 하이픈 4개로 이루어 진 그룹으로 형성되어야 한다.
8–4–4–4–12 => 12345678-1234-1234-1234-1234567890ab
이후 해당 값을 서버로 전송하고 응답값을 받아 처리하면된다.
// jwt 최신버전에서 사용
// use Firebase\JWT\JWT;
// use Firebase\JWT\Key;
function sendConsumption($bundleId, $originalTransactionId, $msg)
{
$outData = array();
$outData['result'] = false;
$outData['msg'] = '';
$apple_API_key_id = 'api 용 키페어아이디';
$keyfile = "file://AuthKey_{$apple_API_key_id}.p8"; // absolute path
$key = openssl_pkey_get_private($keyfile);
$data = ['iss' => 'apple_issuer_id','iat' => time(),'exp' => time() + 1200,'aud' => "appstoreconnect-v1",'nonce' => $this->getUuidV4(),'bid' => $bundleId];
$client_secret = JWT::encode($data, $apple_API_key_id), $key, "ES256");
$url = "https://api.storekit.itunes.apple.com/inApps/v1/transactions/consumption/{$originalTransactionId}";
// only needed for PHP prior to 5.5.24
if (!defined('CURL_HTTP_VERSION_2_0'))
{
define('CURL_HTTP_VERSION_2_0', 3);
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_PORT, 443);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $client_secret","Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $msg);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HEADER, 1);
$response = curl_exec($ch);
if ($response === FALSE)
{
$outData['result'] = false;
$outData['msg'] = curl_error($ch);
}
else
{
$outData['result'] = true;
$outData['msg'] = $response;
// $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
}
return $outData;
}
이렇게 소모성 아이템에 대한 환불 요청에 대한 응답을 발송하면된다.
그후 환불이 발생할경우 환불건에 대한 것을 서버에서 아이템 지급 취소나 회수를 하면된다.
JWT 는 https://github.com/firebase/php-jwt 에서 JWT 라이브러리를 다운로드 받아 사용하면된다.
'iOS' 카테고리의 다른 글
맥북에어 15 인치 드디어 정식 발표 공개 (2) | 2023.06.07 |
---|---|
Apple iOS PUSH php jwt 방식 전송. (0) | 2021.12.28 |
Apple iOS PUSH 인증서 갱신 (0) | 2020.01.30 |