diff --git a/scripts/paddle-production-prices.sh b/scripts/paddle-production-prices.sh index d540f6f..f17726d 100755 --- a/scripts/paddle-production-prices.sh +++ b/scripts/paddle-production-prices.sh @@ -1,222 +1,257 @@ #!/usr/bin/env bash -# pdl_live_apikey_01m3sj642qagm8h0r9z6yqrgzs_3jR1damnQ1XTyA23MpmkTM_Aka - set -euo pipefail -# --------------------------------------------------------------------------- -# SonarDesk - Paddle Production Price Setup -# -# Creates: -# Personal Monthly -# Personal Annual -# Professional Monthly -# Professional Annual -# -# against an EXISTING Paddle production product. -# -# Usage: -# PADDLE_API_KEY="pdl_live_apikey_..." \ -# PADDLE_PRODUCT_ID="pro_..." \ -# ./scripts/paddle-production-prices.sh -# --------------------------------------------------------------------------- - PADDLE_API_URL="https://api.paddle.com" -: "${PADDLE_API_KEY:?PADDLE_API_KEY is required}" -: "${PADDLE_PRODUCT_ID:?PADDLE_PRODUCT_ID is required}" +: "${PADDLE_API_KEY:?PADDLE_API_KEY is not set}" +: "${PADDLE_PRODUCT_ID:?PADDLE_PRODUCT_ID is not set}" -if [[ "${PADDLE_API_KEY}" != pdl_live_apikey_* ]]; then - echo "ERROR: This does not look like a Paddle LIVE API key." - echo "Refusing to continue." - exit 1 -fi - -if [[ "${PADDLE_PRODUCT_ID}" != pro_* ]]; then - echo "ERROR: PADDLE_PRODUCT_ID must start with pro_" - exit 1 -fi - -command -v curl >/dev/null 2>&1 || { +command -v curl >/dev/null || { echo "ERROR: curl is required." exit 1 } -command -v jq >/dev/null 2>&1 || { +command -v jq >/dev/null || { echo "ERROR: jq is required." - echo "Install it on macOS with: brew install jq" exit 1 } -echo -echo "============================================================" -echo " SonarDesk Paddle PRODUCTION Price Setup" -echo "============================================================" -echo -echo "API: ${PADDLE_API_URL}" -echo "Product: ${PADDLE_PRODUCT_ID}" -echo -echo "WARNING: This will create REAL Paddle production prices." -echo - -read -r -p "Continue? Type PRODUCTION to continue: " confirmation - -if [[ "${confirmation}" != "PRODUCTION" ]]; then - echo "Cancelled." - exit 0 +if [[ "$PADDLE_API_KEY" != pdl_live_apikey_* ]]; then + echo "ERROR: This script requires a Paddle LIVE API key." + exit 1 fi -# --------------------------------------------------------------------------- -# Pricing -# -# Paddle unit_price amounts use the smallest currency unit: -# £9.99 = 999 -# £99.00 = 9900 -# -# Change these four values BEFORE running if required. -# --------------------------------------------------------------------------- +if [[ "$PADDLE_PRODUCT_ID" != pro_* ]]; then + echo "ERROR: Invalid Paddle product ID." + exit 1 +fi -CURRENCY="GBP" +echo "============================================" +echo " SonarDesk Paddle Production Price Setup" +echo "============================================" +echo +echo "Product: $PADDLE_PRODUCT_ID" +echo -PERSONAL_MONTHLY_AMOUNT="999" -PERSONAL_ANNUAL_AMOUNT="9990" +# ------------------------------------------------------------ +# Verify product +# ------------------------------------------------------------ -PROFESSIONAL_MONTHLY_AMOUNT="1999" -PROFESSIONAL_ANNUAL_AMOUNT="19990" +PRODUCT_RESPONSE="$( + curl -fsS \ + -H "Authorization: Bearer $PADDLE_API_KEY" \ + -H "Accept: application/json" \ + "$PADDLE_API_URL/products/$PADDLE_PRODUCT_ID" +)" + +PRODUCT_NAME="$(echo "$PRODUCT_RESPONSE" | jq -r '.data.name // empty')" + +if [[ -z "$PRODUCT_NAME" ]]; then + echo "ERROR: Could not retrieve Paddle product." + exit 1 +fi + +echo "Found product: $PRODUCT_NAME" +echo + +# ------------------------------------------------------------ +# Existing prices +# ------------------------------------------------------------ + +get_prices() { + curl -fsS \ + -H "Authorization: Bearer $PADDLE_API_KEY" \ + -H "Accept: application/json" \ + "$PADDLE_API_URL/prices?product_id=$PADDLE_PRODUCT_ID&status=active&per_page=100" +} + +find_price_by_description() { + local description="$1" + + get_prices | + jq -r \ + --arg description "$description" \ + '.data[] + | select(.description == $description) + | .id' | + head -n 1 +} create_price() { local description="$1" local amount="$2" local interval="$3" - echo - echo "Creating: ${description}" + echo "Creating: $description" - local payload - - payload=$(jq -n \ - --arg product_id "${PADDLE_PRODUCT_ID}" \ - --arg description "${description}" \ - --arg amount "${amount}" \ - --arg currency "${CURRENCY}" \ - --arg interval "${interval}" \ - '{ - product_id: $product_id, - description: $description, - unit_price: { - amount: $amount, - currency_code: $currency - }, - billing_cycle: { - interval: $interval, - frequency: 1 - }, - quantity: { - minimum: 1, - maximum: 1 - }, - tax_mode: "account_setting" - }' - ) - - local response - local http_code - local response_file - - response_file=$(mktemp) - - http_code=$(curl \ - --silent \ - --show-error \ - --output "${response_file}" \ - --write-out "%{http_code}" \ - --request POST \ - --url "${PADDLE_API_URL}/prices" \ - --header "Authorization: Bearer ${PADDLE_API_KEY}" \ - --header "Content-Type: application/json" \ - --header "Accept: application/json" \ - --data "${payload}") - - response=$(cat "${response_file}") - rm -f "${response_file}" - - if [[ "${http_code}" -lt 200 || "${http_code}" -ge 300 ]]; then - echo - echo "ERROR creating ${description}" - echo "HTTP ${http_code}" - echo - - echo "${response}" | jq . 2>/dev/null || echo "${response}" - - exit 1 - fi - - local price_id - - price_id=$(echo "${response}" | jq -r '.data.id // empty') - - if [[ -z "${price_id}" ]]; then - echo "ERROR: Paddle created the price but no price ID was returned." - echo "${response}" | jq . - exit 1 - fi - - echo "Created ${description}: ${price_id}" - - printf '%s' "${price_id}" + curl -fsS \ + -X POST \ + "$PADDLE_API_URL/prices" \ + -H "Authorization: Bearer $PADDLE_API_KEY" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + --data "$( + jq -n \ + --arg product "$PADDLE_PRODUCT_ID" \ + --arg description "$description" \ + --arg amount "$amount" \ + --arg interval "$interval" \ + '{ + product_id: $product, + description: $description, + unit_price: { + amount: $amount, + currency_code: "GBP" + }, + billing_cycle: { + interval: $interval, + frequency: 1 + }, + quantity: { + minimum: 1, + maximum: 1 + }, + tax_mode: "account_setting" + }' + )" | + jq -r '.data.id' } -echo -echo "Creating SonarDesk production prices..." +update_price_amount() { + local price_id="$1" + local description="$2" + local amount="$3" + + echo "Updating: $description ($price_id)" + + curl -fsS \ + -X PATCH \ + "$PADDLE_API_URL/prices/$price_id" \ + -H "Authorization: Bearer $PADDLE_API_KEY" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + --data "$( + jq -n \ + --arg description "$description" \ + --arg amount "$amount" \ + '{ + description: $description, + unit_price: { + amount: $amount, + currency_code: "GBP" + } + }' + )" >/dev/null +} + +ensure_price() { + local description="$1" + local amount="$2" + local interval="$3" + + local existing + existing="$(find_price_by_description "$description")" + + if [[ -n "$existing" ]]; then + local current_amount + current_amount="$( + get_prices | + jq -r \ + --arg id "$existing" \ + '.data[] + | select(.id == $id) + | .unit_price.amount' + )" + + if [[ "$current_amount" != "$amount" ]]; then + echo "Existing price $existing has amount $current_amount." + echo "Required amount is $amount." + update_price_amount \ + "$existing" \ + "$description" \ + "$amount" + else + echo "Exists: $description ($existing)" + fi + + echo "$existing" + return + fi + + create_price \ + "$description" \ + "$amount" \ + "$interval" +} + +# ------------------------------------------------------------ +# SonarDesk production prices +# +# Paddle amounts are in pennies: +# +# £2.99 = 299 +# £24.99 = 2499 +# £4.99 = 499 +# £44.99 = 4499 +# ------------------------------------------------------------ + +echo "Configuring prices..." echo -PERSONAL_MONTHLY_ID=$( - create_price \ +PERSONAL_MONTHLY="$( + ensure_price \ "SonarDesk Personal Monthly" \ - "${PERSONAL_MONTHLY_AMOUNT}" \ - "month" | tail -1 -) + "299" \ + "month" | + tail -n 1 +)" -PERSONAL_ANNUAL_ID=$( - create_price \ +PERSONAL_ANNUAL="$( + ensure_price \ "SonarDesk Personal Annual" \ - "${PERSONAL_ANNUAL_AMOUNT}" \ - "year" | tail -1 -) + "2499" \ + "year" | + tail -n 1 +)" -PROFESSIONAL_MONTHLY_ID=$( - create_price \ +PROFESSIONAL_MONTHLY="$( + ensure_price \ "SonarDesk Professional Monthly" \ - "${PROFESSIONAL_MONTHLY_AMOUNT}" \ - "month" | tail -1 -) + "499" \ + "month" | + tail -n 1 +)" -PROFESSIONAL_ANNUAL_ID=$( - create_price \ +PROFESSIONAL_ANNUAL="$( + ensure_price \ "SonarDesk Professional Annual" \ - "${PROFESSIONAL_ANNUAL_AMOUNT}" \ - "year" | tail -1 -) + "4499" \ + "year" | + tail -n 1 +)" echo +echo "============================================" +echo " Paddle Production Prices" +echo "============================================" echo -echo "============================================================" -echo " Paddle Production Prices Created" -echo "============================================================" +echo "Personal Monthly: $PERSONAL_MONTHLY" +echo "Personal Annual: $PERSONAL_ANNUAL" +echo "Professional Monthly: $PROFESSIONAL_MONTHLY" +echo "Professional Annual: $PROFESSIONAL_ANNUAL" echo -echo "Add the following to your PRODUCTION .env:" +echo "Add these to the production .env:" echo echo "PADDLE_ENVIRONMENT=production" echo "PADDLE_API_URL=https://api.paddle.com" -echo -echo "PADDLE_PRODUCT_ID=${PADDLE_PRODUCT_ID}" -echo -echo "PADDLE_PERSONAL_MONTHLY_PRICE_ID=${PERSONAL_MONTHLY_ID}" -echo "PADDLE_PERSONAL_ANNUAL_PRICE_ID=${PERSONAL_ANNUAL_ID}" -echo "PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID=${PROFESSIONAL_MONTHLY_ID}" -echo "PADDLE_PROFESSIONAL_ANNUAL_PRICE_ID=${PROFESSIONAL_ANNUAL_ID}" +echo "PADDLE_PRODUCT_ID=$PADDLE_PRODUCT_ID" +echo "PADDLE_PERSONAL_MONTHLY_PRICE_ID=$PERSONAL_MONTHLY" +echo "PADDLE_PERSONAL_ANNUAL_PRICE_ID=$PERSONAL_ANNUAL" +echo "PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID=$PROFESSIONAL_MONTHLY" +echo "PADDLE_PROFESSIONAL_ANNUAL_PRICE_ID=$PROFESSIONAL_ANNUAL" echo echo "IMPORTANT:" -echo "Do not run this script again unless you intentionally want another" -echo "set of Paddle production prices." +echo "PADDLE_API_KEY and PADDLE_CLIENT_SIDE_TOKEN must also be LIVE values." +echo "PADDLE_WEBHOOK_SECRET must come from the LIVE webhook destination." echo