diff --git a/scripts/lookupOrderId.js b/scripts/lookupOrderId.js new file mode 100644 index 0000000..185d241 --- /dev/null +++ b/scripts/lookupOrderId.js @@ -0,0 +1,57 @@ +const { AppStoreServerAPIClient, SignedDataVerifier } = require("@apple/app-store-server-library"); +require("dotenv").config(); +const fs = require('fs'); + +function createAppStoreServerAPIClientFromEnv() { + const issuerId = process.env.IAP_ISSUER_ID; + const keyId = process.env.IAP_KEY_ID; + const bundleId = process.env.IAP_BUNDLE_ID; + const filePath = process.env.IAP_PRIVATE_KEY_PATH; + const encodedKey = fs.readFileSync(filePath, "utf8"); + const environment = process.env.IAP_ENVIRONMENT === "Sandbox" ? "Sandbox" : "Production"; + + return new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); +} + +async function lookUpOrder(orderId) { + const client = createAppStoreServerAPIClientFromEnv(); + const rootCaDir = process.env.IAP_ROOT_CA_DIR || './apple-root-ca'; + const bundleId = process.env.IAP_BUNDLE_ID; + const environment = process.env.IAP_ENVIRONMENT; + + try { + const transactions = await client.lookUpOrderId(orderId); + const rootCAs = readCertificateFiles(rootCaDir); + const decodedTransactions = await verifyAndDecodeTransactions(transactions.signedTransactions, rootCAs, environment, bundleId); + const transactionIds = decodedTransactions.map(transaction => transaction.transactionId); + console.log("Transaction IDs:", transactionIds); + } catch (error) { + console.error("Error looking up order ID:", error); + } +} + +function readCertificateFiles(directory) { + const files = fs.readdirSync(directory); + return files.map((fileName) => { + const filePath = `${directory}/${fileName}`; + return fs.readFileSync(filePath); + }); +} + +async function verifyAndDecodeTransactions(transactions, rootCAs, environment, bundleId) { + const verifier = new SignedDataVerifier(rootCAs, true, environment, bundleId); + let decodedTransactions = []; + for (let transaction of transactions) { + const decodedTransaction = await verifier.verifyAndDecodeTransaction(transaction); + decodedTransactions.push(decodedTransaction); + } + return decodedTransactions; +} + +const orderId = process.argv[2]; +if (!orderId) { + console.error("Please provide an order ID as a command line argument."); + process.exit(1); +} + +lookUpOrder(orderId); \ No newline at end of file diff --git a/src/app_store_receipt_verifier.js b/src/app_store_receipt_verifier.js index 997a89b..dd5526d 100644 --- a/src/app_store_receipt_verifier.js +++ b/src/app_store_receipt_verifier.js @@ -315,7 +315,19 @@ function createAppStoreServerAPIClientFromEnv() { return new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); } -/** + +async function processOrderToTransactions(order_id) { + const client = createAppStoreServerAPIClientFromEnv(); + const rootCaDir = process.env.IAP_ROOT_CA_DIR || './apple-root-ca'; + const bundleId = process.env.IAP_BUNDLE_ID; + const environment = process.env.IAP_ENVIRONMENT; + + const transactions = await client.lookUpOrderId(order_id); + const rootCAs = readCertificateFiles(rootCaDir); + return await verifyAndDecodeTransactions(transactions.signedTransactions, rootCAs, environment, bundleId); +} + +/** * Gets the App Store environment from the environment variables. * * @returns {Environment} The App Store environment. @@ -325,5 +337,5 @@ function getAppStoreEnvironmentFromEnv() { } module.exports = { - verify_receipt, verify_transaction_id, lookup_order_id + verify_receipt, verify_transaction_id, lookup_order_id, processOrderToTransactions }; diff --git a/src/router_config.js b/src/router_config.js index 5e39bb1..d810a09 100644 --- a/src/router_config.js +++ b/src/router_config.js @@ -1,7 +1,7 @@ const { json_response, simple_response, error_response, invalid_request, unauthorized_response } = require('./server_helpers') const { create_account, get_account_info_payload, check_account, get_account, put_account, get_account_and_user_id, get_user_uuid, delete_account, add_successful_transactions_to_account } = require('./user_management') const handle_translate = require('./translate') -const { verify_receipt, verify_transaction_id } = require('./app_store_receipt_verifier'); +const { verify_receipt, verify_transaction_id, processOrderToTransactions } = require('./app_store_receipt_verifier'); const bodyParser = require('body-parser') const cors = require('cors'); const { required_nip98_auth, capture_raw_body, optional_nip98_auth } = require('./nip98_auth') @@ -518,7 +518,45 @@ function config_router(app) { } json_response(res, new_checkout_object) }) - + + router.post('/admin/users/add-transaction-from-order-id', async (req, res) => { + const { order_id, pubkey, admin_password } = req.body; + + if (!admin_password || admin_password !== process.env.ADMIN_PASSWORD) { + unauthorized_response(res, 'Invalid admin password'); + return; + } + + if (!order_id || !pubkey) { + invalid_request(res, 'Missing order_id or pubkey'); + return; + } + + const { account } = get_account_and_user_id(app, pubkey); + if (!account) { + simple_response(res, 404); + return; + } + + try { + const decodedTransactions = await processOrderToTransactions(order_id); + + if (decodedTransactions.length === 0) { + simple_response(res, 404); + return; + } + + const { account: new_account, user_id: latest_user_id, request_error } = add_successful_transactions_to_account(app, account.pubkey, decodedTransactions); + if (request_error) { + error_response(res, request_error); + return; + } + json_response(res, get_account_info_payload(latest_user_id, new_account)) + } catch (error) { + error_response(res, `Error processing order_id: ${error.message}`); + } + }); + if (process.env.ENABLE_DEBUG_ENDPOINTS == "true") { /** * This route is used to delete a user account.