Billing: fatture manuali, profilo obbligatorio e piani Stripe mensile/annuale.
Rimuove link al portale Stripe in area cliente, aggiunge flusso admin per PDF fattura e email, blocca checkout senza dati di fatturazione, allinea prezzi a €4,90/€39,90 e €9,90/€69,90, locale italiano e documentazione deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
53
scripts/merge_production_env.sh
Executable file
53
scripts/merge_production_env.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unisce infra/.env.production.example in infra/.env senza sovrascrivere valori già impostati.
|
||||
# Uso locale: ./scripts/merge_production_env.sh
|
||||
# Sul server: REMOTE=1 ./scripts/merge_production_env.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
EXAMPLE="$ROOT/infra/.env.production.example"
|
||||
REMOTE="${REMOTE:-0}"
|
||||
SERVER="${DEPLOY_SERVER:-eminux@192.168.1.146}"
|
||||
ENV_PATH="${ENV_PATH:-/opt/matchlivetv/infra/.env}"
|
||||
|
||||
if [[ "$REMOTE" == "1" ]]; then
|
||||
scp "$EXAMPLE" "$SERVER:/tmp/matchlivetv-env.example"
|
||||
ssh "$SERVER" "REMOTE_ENV='$ENV_PATH' bash -s" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
EXAMPLE=/tmp/matchlivetv-env.example
|
||||
ENV="${REMOTE_ENV}"
|
||||
touch "$ENV"
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
|
||||
key="${line%%=*}"
|
||||
[[ "$key" == "$line" ]] && continue
|
||||
if ! grep -q "^${key}=" "$ENV" 2>/dev/null; then
|
||||
echo "$line" >> "$ENV"
|
||||
echo " + aggiunto: $key"
|
||||
fi
|
||||
done < "$EXAMPLE"
|
||||
echo ""
|
||||
echo "File: $ENV ($(wc -l < "$ENV") righe)"
|
||||
echo "Chiavi presenti:"
|
||||
grep -E '^[A-Z_]+=' "$ENV" | cut -d= -f1 | sort
|
||||
echo ""
|
||||
echo "Chiavi VUOTE (da compilare):"
|
||||
grep -E '^[A-Z_]+=$' "$ENV" | cut -d= -f1 || echo " (nessuna)"
|
||||
REMOTE
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ENV="${ENV_FILE:-$ROOT/infra/.env}"
|
||||
touch "$ENV"
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
|
||||
key="${line%%=*}"
|
||||
[[ "$key" == "$line" ]] && continue
|
||||
if ! grep -q "^${key}=" "$ENV" 2>/dev/null; then
|
||||
echo "$line" >> "$ENV"
|
||||
echo " + aggiunto: $key"
|
||||
fi
|
||||
done < "$EXAMPLE"
|
||||
echo "Fatto: $ENV"
|
||||
121
scripts/setup_stripe_production.sh
Executable file
121
scripts/setup_stripe_production.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configura Stripe sul server di produzione e verifica l'integrazione.
|
||||
#
|
||||
# Uso:
|
||||
# ./scripts/setup_stripe_production.sh # legge infra/.env locale
|
||||
# ./scripts/setup_stripe_production.sh --webhook-secret whsec_xxx
|
||||
# STRIPE_MODE=live ./scripts/setup_stripe_production.sh # usa chiavi live nel .env
|
||||
#
|
||||
# Prima di eseguire in LIVE:
|
||||
# 1. Stripe Dashboard → modalità Live → crea prodotti/prezzi → copia price_...
|
||||
# 2. Webhook endpoint: https://www.matchlivetv.it/webhooks/stripe
|
||||
# Eventi: checkout.session.completed, customer.subscription.*, invoice.paid, invoice.payment_failed
|
||||
# 3. Incolla whsec_... live in infra/.env come STRIPE_WEBHOOK_SECRET
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-$ROOT/infra/.env}"
|
||||
SERVER="${DEPLOY_SERVER:-eminux@192.168.1.146}"
|
||||
REMOTE_DIR="${DEPLOY_PATH:-/opt/matchlivetv}"
|
||||
WEBHOOK_SECRET=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--env-file) ENV_FILE="$2"; shift 2 ;;
|
||||
--webhook-secret) WEBHOOK_SECRET="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
sed -n '2,14p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Argomento sconosciuto: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "File non trovato: $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_env() {
|
||||
local key="$1"
|
||||
grep -E "^${key}=" "$ENV_FILE" | head -1 | cut -d= -f2- | tr -d '\r'
|
||||
}
|
||||
|
||||
STRIPE_SECRET_KEY="$(read_env STRIPE_SECRET_KEY)"
|
||||
STRIPE_PREMIUM_LIGHT_PRICE_ID="$(read_env STRIPE_PREMIUM_LIGHT_PRICE_ID)"
|
||||
STRIPE_PREMIUM_FULL_PRICE_ID="$(read_env STRIPE_PREMIUM_FULL_PRICE_ID)"
|
||||
[[ -z "$WEBHOOK_SECRET" ]] && WEBHOOK_SECRET="$(read_env STRIPE_WEBHOOK_SECRET)"
|
||||
|
||||
STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID="${STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID:-$(read_env STRIPE_PREMIUM_LIGHT_PRICE_ID)}"
|
||||
STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID="${STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID:-$(read_env STRIPE_PREMIUM_FULL_PRICE_ID)}"
|
||||
STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID="${STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID:-}"
|
||||
STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID="${STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID:-}"
|
||||
|
||||
for var in STRIPE_SECRET_KEY STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
echo "Manca $var in $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$WEBHOOK_SECRET" ]]; then
|
||||
echo "Manca STRIPE_WEBHOOK_SECRET." >&2
|
||||
echo "Crea un endpoint in Stripe → https://www.matchlivetv.it/webhooks/stripe" >&2
|
||||
echo "Poi: $0 --webhook-secret whsec_..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${STRIPE_SECRET_KEY}" == sk_test_* ]]; then
|
||||
echo "Modalità: TEST (sk_test_) — pagamenti solo con carte di prova Stripe."
|
||||
elif [[ "${STRIPE_SECRET_KEY}" == sk_live_* ]]; then
|
||||
echo "Modalità: LIVE (sk_live_) — pagamenti reali."
|
||||
else
|
||||
echo "ATTENZIONE: STRIPE_SECRET_KEY non sembra sk_test_ né sk_live_" >&2
|
||||
fi
|
||||
|
||||
echo "Aggiorno $SERVER:$REMOTE_DIR/infra/.env ..."
|
||||
|
||||
ssh "$SERVER" "bash -s" <<REMOTE
|
||||
set -euo pipefail
|
||||
ENV="$REMOTE_DIR/infra/.env"
|
||||
touch "\$ENV"
|
||||
upsert() {
|
||||
local key="\$1" val="\$2"
|
||||
if grep -q "^\${key}=" "\$ENV" 2>/dev/null; then
|
||||
sed -i "s|^\${key}=.*|\${key}=\${val}|" "\$ENV"
|
||||
else
|
||||
echo "\${key}=\${val}" >> "\$ENV"
|
||||
fi
|
||||
}
|
||||
upsert STRIPE_SECRET_KEY '${STRIPE_SECRET_KEY}'
|
||||
upsert STRIPE_WEBHOOK_SECRET '${WEBHOOK_SECRET}'
|
||||
upsert STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID '${STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID}'
|
||||
upsert STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID '${STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID}'
|
||||
upsert STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID '${STRIPE_PREMIUM_FULL_MONTHLY_PRICE_ID}'
|
||||
upsert STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID '${STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID}'
|
||||
upsert STRIPE_PREMIUM_LIGHT_PRICE_ID '${STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID}'
|
||||
upsert STRIPE_PREMIUM_FULL_PRICE_ID '${STRIPE_PREMIUM_FULL_YEARLY_PRICE_ID}'
|
||||
REMOTE
|
||||
|
||||
echo "Sync codice..."
|
||||
bash "$ROOT/scripts/deploy/sync_to_server.sh"
|
||||
|
||||
echo "Rebuild rails + seed piani..."
|
||||
ssh "$SERVER" "cd $REMOTE_DIR/infra && docker compose -f docker-compose.prod.yml --env-file .env up -d --build rails"
|
||||
sleep 20
|
||||
ssh "$SERVER" "cd $REMOTE_DIR/infra && docker compose -f docker-compose.prod.yml exec -T rails bundle exec rails db:seed" || true
|
||||
|
||||
echo "Verifica..."
|
||||
ssh "$SERVER" "cd $REMOTE_DIR/infra && docker compose -f docker-compose.prod.yml exec -T rails bundle exec rails runner \"
|
||||
puts 'stripe_enabled=' + MatchLiveTv.stripe_enabled?.to_s
|
||||
puts 'light_price=' + (Plan['premium_light'].stripe_price_id || 'MISSING').to_s
|
||||
puts 'full_price=' + (Plan['premium_full'].stripe_price_id || 'MISSING').to_s
|
||||
\""
|
||||
|
||||
echo ""
|
||||
echo "Prossimo passo: test browser"
|
||||
echo " 1. Login su https://www.matchlivetv.it/login"
|
||||
echo " 2. Società → Abbonamento → Attiva Premium Light"
|
||||
echo " 3. Carta test 4242 4242 4242 4242 (solo se sk_test_)"
|
||||
echo " 4. Stripe Dashboard → Webhooks → verifica eventi 200 su /webhooks/stripe"
|
||||
83
scripts/test_billing_intervals.sh
Executable file
83
scripts/test_billing_intervals.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test scelta mensile/annuale + redirect Checkout con price id corretto
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
COMPOSE="docker compose -f $ROOT/infra/docker-compose.yml"
|
||||
BASE="${BASE_URL:-http://localhost:3000}"
|
||||
COOKIES=$(mktemp)
|
||||
trap 'rm -f "$COOKIES"' EXIT
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
ok() { echo "OK $1"; pass=$((pass+1)); }
|
||||
ko() { echo "FAIL $1"; fail=$((fail+1)); }
|
||||
|
||||
read_env() {
|
||||
grep -E "^$1=" "$ROOT/infra/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r' || true
|
||||
}
|
||||
|
||||
YEARLY_LIGHT=$(read_env STRIPE_PREMIUM_LIGHT_YEARLY_PRICE_ID)
|
||||
[[ -z "$YEARLY_LIGHT" ]] && YEARLY_LIGHT=$(read_env STRIPE_PREMIUM_LIGHT_PRICE_ID)
|
||||
MONTHLY_LIGHT=$(read_env STRIPE_PREMIUM_LIGHT_MONTHLY_PRICE_ID)
|
||||
|
||||
login() {
|
||||
curl -sf -c "$COOKIES" "$BASE/login" -o /tmp/mltv_login.html
|
||||
T=$(grep -oP 'name="authenticity_token" value="\K[^"]+' /tmp/mltv_login.html | head -1)
|
||||
curl -sf -c "$COOKIES" -b "$COOKIES" -X POST "$BASE/login" \
|
||||
-d "authenticity_token=$T" -d "email=coach@matchlivetv.test" -d "password=password123" -o /dev/null
|
||||
}
|
||||
|
||||
echo "== Test intervalli billing =="
|
||||
|
||||
$COMPOSE exec -T rails bundle exec rails db:migrate >/dev/null 2>&1 || true
|
||||
|
||||
$COMPOSE exec -T rails bundle exec rails runner "
|
||||
club = Club.first
|
||||
club.update!(
|
||||
billing_legal_name: 'ASD Test', billing_email: 'bill@test.it',
|
||||
billing_address_line: 'Via 1', billing_city: 'Milano', billing_postal_code: '20100',
|
||||
billing_vat_number: '12345678901', billing_recipient_code: 'ABCDEFG'
|
||||
)
|
||||
sub = club.subscription || club.create_subscription!(plan: Plan['free'], status: 'active')
|
||||
sub.update!(plan: Plan['free'], stripe_subscription_id: nil, billing_interval: nil)
|
||||
puts club.id
|
||||
" >/dev/null 2>&1 && ok "setup club billing" || ko "setup club billing"
|
||||
|
||||
CLUB_ID=$($COMPOSE exec -T rails bundle exec rails runner "puts Club.first.id" 2>/dev/null | tail -1)
|
||||
|
||||
login && ok "login" || ko "login"
|
||||
|
||||
BODY=$(curl -sf -b "$COOKIES" "$BASE/clubs/$CLUB_ID/billing")
|
||||
echo "$BODY" | grep -q "Attiva Premium Light — €40/anno" && ok "UI bottone annuale Light" || ko "UI bottone annuale Light"
|
||||
if [[ -n "$MONTHLY_LIGHT" ]]; then
|
||||
echo "$BODY" | grep -q "Attiva Premium Light — €5/mese" && ok "UI bottone mensile Light" || ko "UI bottone mensile Light"
|
||||
else
|
||||
echo "$BODY" | grep -q "€5/mese" && ko "UI mensile assente se non configurato" || ok "UI mensile nascosto (price id mancante)"
|
||||
fi
|
||||
|
||||
if [[ -n "$YEARLY_LIGHT" ]]; then
|
||||
HEADERS=$(curl -s -D - -o /dev/null -b "$COOKIES" "$BASE/clubs/$CLUB_ID/checkout?plan=premium_light&interval=yearly" | tr -d '\r')
|
||||
if echo "$HEADERS" | grep -qi "location:.*checkout\.stripe\.com"; then
|
||||
ok "redirect checkout annuale"
|
||||
else
|
||||
ko "redirect checkout annuale"
|
||||
fi
|
||||
|
||||
PRICE_USED=$($COMPOSE exec -T rails bundle exec rails runner "
|
||||
puts Billing::Stripe::PriceCatalog.price_id(plan_slug: 'premium_light', interval: 'yearly')
|
||||
" 2>/dev/null | tail -1)
|
||||
[[ "$PRICE_USED" == "$YEARLY_LIGHT" ]] && ok "price id annuale corretto ($PRICE_USED)" || ko "price id annuale ($PRICE_USED vs $YEARLY_LIGHT)"
|
||||
fi
|
||||
|
||||
$COMPOSE exec -T rails bundle exec rspec \
|
||||
spec/services/billing/stripe/price_catalog_spec.rb \
|
||||
spec/services/billing/stripe/checkout_session_spec.rb \
|
||||
spec/services/billing/stripe/change_plan_spec.rb \
|
||||
spec/helpers/public/billing_helper_spec.rb \
|
||||
--format progress 2>&1 | tail -3
|
||||
RS=$?
|
||||
[[ $RS -eq 0 ]] && ok "RSpec billing intervals" || ko "RSpec billing intervals"
|
||||
|
||||
echo ""
|
||||
echo "Risultato: $pass OK, $fail FAIL"
|
||||
[[ "$fail" -eq 0 ]]
|
||||
61
scripts/test_stripe_e2e.sh
Executable file
61
scripts/test_stripe_e2e.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke test pagamenti: pagina billing + redirect Checkout (+ opzionale profilo completo).
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${BASE_URL:-https://www.matchlivetv.it}"
|
||||
EMAIL="${TEST_EMAIL:-coach@matchlivetv.test}"
|
||||
PASSWORD="${TEST_PASSWORD:-password123}"
|
||||
PLAN="${TEST_PLAN:-premium_light}"
|
||||
COOKIES=$(mktemp)
|
||||
trap 'rm -f "$COOKIES"' EXIT
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
ok() { echo "OK $1"; pass=$((pass+1)); }
|
||||
ko() { echo "FAIL $1"; fail=$((fail+1)); }
|
||||
|
||||
echo "== Stripe E2E smoke @ $BASE =="
|
||||
|
||||
curl -sf "$BASE/up" >/dev/null && ok "health /up" || ko "health /up"
|
||||
|
||||
curl -sf -c "$COOKIES" "$BASE/login" -o /tmp/mltv_login.html
|
||||
T=$(grep -oP 'name="authenticity_token" value="\K[^"]+' /tmp/mltv_login.html | head -1 || true)
|
||||
if [[ -z "$T" ]]; then
|
||||
ko "login page (token CSRF)"
|
||||
else
|
||||
CODE=$(curl -s -o /dev/null -w "%{http_code}" -c "$COOKIES" -b "$COOKIES" -X POST "$BASE/login" \
|
||||
-d "authenticity_token=$T" -d "email=$EMAIL" -d "password=$PASSWORD")
|
||||
[[ "$CODE" == "302" || "$CODE" == "303" ]] && ok "login $EMAIL" || ko "login ($CODE)"
|
||||
fi
|
||||
|
||||
CLUB_LINK=$(curl -sf -b "$COOKIES" "$BASE/" | grep -oP 'href="/clubs/[^"]+/billing"' | head -1 || true)
|
||||
if [[ -z "$CLUB_LINK" ]]; then
|
||||
# prova da pagina clubs
|
||||
CLUB_ID=$(curl -sf -b "$COOKIES" "$BASE/" | grep -oP '/clubs/[0-9a-f-]{36}' | head -1 | sed 's|/clubs/||' || true)
|
||||
CLUB_LINK="/clubs/${CLUB_ID}/billing"
|
||||
fi
|
||||
|
||||
if [[ -z "${CLUB_LINK:-}" || "$CLUB_LINK" == "/clubs//billing" ]]; then
|
||||
ko "trova link billing club"
|
||||
else
|
||||
ok "billing path $CLUB_LINK"
|
||||
BODY=$(curl -sf -b "$COOKIES" "$BASE$CLUB_LINK")
|
||||
echo "$BODY" | grep -qi "stripe non configurato" && ko "Stripe abilitato in UI" || ok "Stripe abilitato in UI"
|
||||
echo "$BODY" | grep -q "Attiva Premium" && ok "pulsante Attiva Premium" || ko "pulsante Attiva Premium"
|
||||
echo "$BODY" | grep -q "Dati di fatturazione" && ok "link profilo fatturazione" || ko "link profilo fatturazione"
|
||||
|
||||
CHECKOUT_URL="${CLUB_LINK%/billing}/checkout?plan=${PLAN}"
|
||||
HEADERS=$(curl -s -D - -o /dev/null -b "$COOKIES" "$BASE$CHECKOUT_URL" | tr -d '\r')
|
||||
if echo "$HEADERS" | grep -qi "location:.*checkout\.stripe\.com"; then
|
||||
ok "redirect Stripe Checkout"
|
||||
elif echo "$HEADERS" | grep -qi "location:.*billing/profile"; then
|
||||
ok "redirect profilo fatturazione (dati incompleti — completa prima del pagamento)"
|
||||
else
|
||||
ko "redirect checkout (atteso checkout.stripe.com o billing/profile)"
|
||||
echo "$HEADERS" | grep -i "^location:" | head -3 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Risultato: $pass OK, $fail FAIL"
|
||||
[[ "$fail" -eq 0 ]]
|
||||
Reference in New Issue
Block a user