PayPal Integration (Phase C)
Architettura PayPal — differenze chiave da Stripe
PayPal usa una struttura a 3 livelli:
- Product (catalogo): descrive cosa vendi (id
PROD-xxx) - Billing Plan: pricing structure ricorrente (id
P-xxx) - Subscription: impegno cliente su un Plan (creato al checkout)
Stripe usa 2 livelli (Product + Price), PayPal aggiunge Plan come livello dedicato per le subscription. Per pagamenti one-time si saltano i livelli 2-3 e si usa direttamente Orders API.
Componenti
HWBillingPayPalPayPalClient
Wrapper REST + OAuth con token caching.
Token management:
access_token()→ recupera token OAuthclient_credentialscached in transient- Refresh automatico 5 min prima della scadenza (default PayPal ~9h TTL)
- Cache key:
hwbi_paypal_token_{md5(mode + client_id)}
Endpoint coperti:
/v1/oauth2/token— token generation/v1/catalogs/products— products CRUD/v1/billing/plans— plans CRUD + activate/deactivate/v1/billing/subscriptions— create, get, cancel, suspend, activate/v2/checkout/orders— create, get, capture/v1/notifications/verify-webhook-signature— webhook verification (remote)
Mode: option hwbi_paypal_mode switches api-m.sandbox.paypal.com ↔ api-m.paypal.com.
Idempotency POST: header PayPal-Request-Id: {uuid4} su ogni create.
HWBillingPayPalPayPalProductSync
Push one-way piano locale → PayPal Product + Billing Plan.
- Product: idempotent. Se
metadata.paypal_product_idesiste → PATCH (name/description). Altrimenti POST. - Plan (solo recurring): se
paypal_plan_idesiste E pricing/period invariati → no-op. Altrimentiplan_deactivatevecchio +plan_createnuovo (PayPal plans immutabili su pricing, come Stripe). - One-time products non hanno Plan — usano Orders API direttamente al checkout.
Salva refs in wp_hwbi_plans:
paypal_plan_idcolonna (per Billing Plan)metadata_json.paypal_product_id(per Catalog Product)
REST Endpoints
1. Frontend → POST /checkout/paypal/create-order { plan_slug }
2. Backend: PayPalClient::subscription_create({ plan_id, subscriber, application_context })
3. Backend → response: { approval_url, paypal_id, order_id }
4. Frontend → redirect window to approval_url
5. User approves su PayPal → PayPal redirect a return_url con ?subscription_id=I-xxx&token=xxx
6. Frontend → POST /checkout/paypal/capture { paypal_subscription_id, hwbi_order_id }
7. PayPal → POST /webhooks/paypal con BILLING.SUBSCRIPTION.ACTIVATED
8. Webhook handler: marca order paid + crea wp_hwbi_subscriptions row + enqueue Plesk provisioning
Flow one-time (credit pack):
1. POST /checkout/paypal/create-order → PayPalClient::order_create({ intent: CAPTURE, purchase_units })
2. Frontend redirect ad approval_url
3. User approves → return con ?token=xxx (Order ID)
4. POST /checkout/paypal/capture { paypal_order_id } → order_capture (server-side)
5. PayPal → POST /webhooks/paypal con PAYMENT.CAPTURE.COMPLETED
6. Webhook handler: marca order paid + record payment + enqueue provisioning
Webhook Events gestiti
Signature Verification — perché REMOTA?
PayPal usa firme asimmetriche basate su certificati X.509 + algoritmi multipli. Verificarle localmente richiederebbe:
- Scaricare
paypal-cert-url(cert pubblico) - Validare cert contro CA chain
- RSA verify del
paypal-transmission-sigsu HMAC del payload
Complesso e fragile. PayPal raccomanda chiamare POST /v1/notifications/verify-webhook-signature con i 5 headers + il payload + il webhook_id → loro fanno il check e ritornano verification_status: "SUCCESS" | "FAILURE".
Costo: 1 chiamata API extra per webhook (vs 0 per Stripe), ma robusto e zero codice criptografico nostro.
Setup (admin)
1. Crea App PayPal
- https://developer.paypal.com/dashboard/applications/live (o /sandbox per test)
- “Create App” → tipo: Business → assegna un nome (es. “Hostwebo Billing V2”)
- Copia
Client ID+Secret
2. Configura nel plugin
- wp-admin → Hostwebo Billing → Impostazioni
- Sezione PayPal:
- Modalità:
sandbox(per test) olive(produzione) - Client ID + Secret → incolla
- Salva
3. Crea Webhook su PayPal
- https://developer.paypal.com/dashboard/applications → seleziona la tua app
- Scroll a “Webhooks” → “Add Webhook”
- URL:
https://hostwebo.it/wp-json/hostwebo-billing/v1/webhooks/paypal - Eventi:
Checkout order completedPayment capture completedPayment sale completedPayment sale deniedBilling subscription activatedBilling subscription cancelledBilling subscription expiredBilling subscription suspendedBilling subscription updatedBilling subscription payment failed
- Copia
Webhook ID(formatoWH-xxx) - Plugin Impostazioni → PayPal → Webhook ID → incolla → Salva
4. Sync piani
- wp-admin → Hostwebo Billing → Piani
- Click “🅿️ Sync to PayPal (all)”
- Verifica che ogni piano abbia
paypal_plan_idvalorizzato
Test end-to-end (sandbox)
- Mode = sandbox + Sandbox API keys
- Sync piani → controlla su https://developer.paypal.com/dashboard/notifications/webhooks-events che i Products + Plans esistano
- Frontend: utente beta tester clicca “Acquista” → /v2-checkout/ → seleziona PayPal
- Approval PayPal con account sandbox personal (creato da PayPal Developer Dashboard)
- Approve → return su /v2-checkout/?paypal_subscription_id=I-xxx → frontend chiama /checkout/paypal/capture
- Webhook PayPal arriva entro 1-30 secondi → mark order paid + subscription created
- Verifica DB:
wp_hwbi_ordersrow con status=’paid’, payment_gateway=’paypal’wp_hwbi_paymentsrow con webhook_event_id univocowp_hwbi_subscriptionsrow (se recurring)wp_hwbi_provisioning_jobsjob pending → processed entro 1min
Filter / Action hooks
Tutti già documentati in stripe-integration.md — gli stessi action hooks emessi da Stripe sono emessi anche da PayPal:
hwbi_order_paid($order_id, $reference)hwbi_subscription_renewed($subscription_id)(su PAYMENT.SALE.COMPLETED)hwbi_subscription_cancelled($paypal_sub_id)(su BILLING.SUBSCRIPTION.CANCELLED)hwbi_subscription_payment_failed($paypal_sub_id)(su BILLING.SUBSCRIPTION.PAYMENT.FAILED)
Plus filter PayPal-specific:
hwbi_paypal_return_url— URL frontend dopo approval (default/v2-checkout/)
Frontend integration (Fase G — page template /v2-checkout/)
In Phase G costruiremo il page-template /v2-checkout/ con UX dual-gateway:
- 2 bottoni: “💳 Paga con carta (Stripe)” + “🅿️ Paga con PayPal”
- Su click PayPal → fetch
/checkout/paypal/create-order→window.location.href = approval_url - Su return da PayPal con
?paypal_subscription_id=...o?token=...→ fetch/checkout/paypal/capture→ redirect a/grazie/
Cose da NON dimenticare
- Sandbox prima: testare sempre prima in mode=sandbox prima di passare a live
- Webhook timing: PayPal può ritardare webhook fino a 30s (vs Stripe <2s). UI deve mostrare "in attesa di conferma" finché order non è paid.
- Currency: hardcoded EUR. Per multi-currency in futuro, aggiungere campo
currencyper piano. - Tax inclusive: prezzi nel nostro catalog sono IVA-incl, settiamo
taxes.inclusive: truesu PayPal Plan. - Trial: PayPal supporta trial via cycle
tenure_type: TRIAL(1 cycle prima del REGULAR). Per ora non implementato — i nostri piani non hanno trial. Se serve, estenderePayPalProductSync::sync_plan.
Questa guida ti è stata utile?
Il tuo feedback ci aiuta a tenere la documentazione utile e aggiornata.
Continua a leggere
DASHBOARD CLIENTE — AUDIT COMPLETO
DASHBOARD CLIENTE — AUDIT COMPLETO # Data: 2026-05-13 Sessione: Step A della trasformazione dashboard (Fase 11) Obiettivo: mappare lo stato attuale per pianificare Step B (foundation) e Step C (hub-by-hub). 📊 1. INVENTAR…
Stripe Integration (Phase B)
Stripe Integration (Phase B) # Componenti # HWBillingStripeStripeClient # Wrapper REST minimale (no Composer). Implementa solo gli endpoint usati: Products: product_create, product_update, product_getPrices: price_create…
Hostwebo Billing — Database Schema
Hostwebo Billing — Database Schema # Tutte le tabelle prefisso wp_hwbi_ (o {wpdb->prefix}hwbi_). Schema completo creato al primo activate via Installer::install_schema(). Tabelle # wp_hwbi_plans # Catalogo prodotti (h…
Plesk customer panel lockout — guida operativa admin
Plesk customer panel lockout — guida operativa admin # Obiettivo: i clienti non devono mai poter loggare nel pannello Plesk diretto. Tutta l’amministrazione hosting passa dalla dashboard Hostwebo (file manager, ema…