function verifyPurchaseAppleStoreKit2(Request $request) {
$sandbox = true;
$transactionID = $request->query("transactionID");
$url = "https://api.storekit.itunes.apple.com/inApps/v1/history/$transactionID";
if ($sandbox) {
$url = "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/history/$transactionID";
}
$handle = curl_init($url);
$token = $this->generateJWTForSigningApplePurchase();
curl_setopt($handle, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
// response includes multiple signedTransactions
$response = curl_exec($handle);
$httpcode = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
curl_close($handle);
// getting a json object from the $response
$json = json_decode($response, false);
return $json;
}
from the above function, you below result:
{
"revision":"1656919939000_2000000096041637",
"bundleId":"com.yourdomain.yourappname.test",
"environment":"Sandbox",
"hasMore":false,
"signedTransactions":[LIST OF SIGNED TRANSACTION]
}
each signedTransaction contains below information.
{
"transactionId":"SOME NUMBER",
"originalTransactionId":"SOME NUMBER",
"bundleId":"com.yourdomain.yourappname.test",
"productId":"com.yourdomain.yourappname.test.in_app_product_name",
"purchaseDate":1644992734000,
"originalPurchaseDate":1644992734000,
"quantity":1,
"type":"Non-Consumable",
"inAppOwnershipType":"PURCHASED",
"signedDate":1658031241903,
"environment":"Sandbox"
}
so, you want to iterate over $json->signedTransactions, and find a match.
foreach($transactions as $transaction) {
}
use Carbon\Carbon;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
public function generateJWTForSigningApplePurchase() {
$signer = new Sha256();
$key = env('JWT_SECRET');
$privateKey = new Key($key);
$time = Carbon::now()->timestamp;
$token = (new Builder())
->issuedBy(env('ISSUER_ID'))
->issuedAt($time)
->expiresAt($time + (60 * 60))
->withClaim('bid', env('BUNDLE_ID'))
->withClaim('aud', "appstoreconnect-v1")
->withHeader('alg', 'ES256')
->withHeader('kid', env('KID'))
->withHeader('typ', 'JWT')
->getToken($signer, $privateKey);
return $token->__toString();
}
Client-side code in Swift
func verify(id: String) {
let urlStringVerifyPurchase = "https://YOUR_DOMAIN.COM/verify/"
Task {
guard let verificationResult = await products.filter({ $0.id == id}).first?.currentEntitlement else { return }
switch verificationResult {
case .verified(let transaction):
// your validation function
validation(transaction: transaction)
let queryItems = [URLQueryItem(name: "transactionID", value: "\(transaction.originalID)")]
var urlComps = URLComponents(string: urlStringVerifyPurchase)!
urlComps.queryItems = queryItems
guard let url = urlComps.url else { return }
let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let data = data else {
print("no data")
return
}
do {
let decode = try JSONDecoder().decode(ServerVerifyPurchaseResponse.self, from: data)
if decode.code == 200 {
self?.performDownload(id: id)
}
} catch {
print("error occurred when decoding verify purchase data: \(error)")
}
}
task.resume()
case .unverified(let transaction, _):
print(transaction)
}
}
}