saveCard = true) var $saveCustomerOnlyIntroHtml = "Inserisci i dati della carta su cui potremo eventualmente fare addebiti"; var $sddIntroHtml = "Inserisci le coordinate del conto corrente di addebito"; var $saveCustomerOnlyButtonText = "Autorizzo addebiti su questa carta"; var $sddButtonText = "Autorizzo addebiti su questo conto corrente"; var $authErrorText = "Errore nei parametri di configurazione Stripe"; var $sddBeneficiario = "TNX srl"; //tutte le lingue qui: https://docs.stripe.com/payments/sepa-debit/set-up-payment?platform=web&payment-ui=direct-api#add-and-configure-an-iban-element //By providing your payment information and confirming this payment, you authorise (A) ####BENEFICIARIO#### and Stripe, our payment service provider, to send instructions to your bank to debit your account and (B) your bank to debit your account in accordance with those instructions. As part of your rights, you are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited. Your rights are explained in a statement that you can obtain from your bank. You agree to receive notifications for future debits up to 2 days before they occur. var $sddAccettazione = "Fornendo i dati di pagamento e confermando il pagamento, l’utente autorizza (A) ##BENEFICIARIO## e Stripe, il fornitore del servizio di pagamento locale, a inviare alla sua banca le istruzioni per eseguire addebiti sul suo conto e (B) la sua banca a effettuare addebiti conformemente a tali istruzioni. L’utente, fra le altre cose, ha diritto a un rimborso dalla banca, in base a termini e condizioni dell’accordo sottoscritto con l’istituto. Il rimborso va richiesto entro otto settimane dalla data dell’addebito sul conto. I diritti dell’utente sono illustrati in una comunicazione riepilogativa che è possibile richiedere alla banca. L’utente accetta di ricevere notifiche per i futuri addebiti fino a due giorni prima che vengano effettuati."; //OLD var $sddAccettazione = "Fornendo l'IBAN e confermando il pagamento, autorizzi TNX Srl e Stripe Inc, gestore dei pagamenti, a inviare istruzioni alla banca per effettuare addebiti sul conto indicato. Avrai la possibilità di chiedere un rimborso alla banca secondo gli accordi contrattuali stipulati. I rimborsi possono essere richiesti entro 8 settimane dalla data di addebito."; var $saveCustomerOnlyEndText = "La procedura di autorizzazione è andata a buon fine"; var $abbonamento = '';//id del piano da sottoscrivere //al completamento della procedura il pagamento non è andato a buon fine, mettere a false per non triggerare confermato() //si possono attivare i webhook "charge.failed" e "charge.succeeded", ma non ho chiaro come ricollegarci all'identificativo del pagamento (in questo evento viene ritornato il customer id, che potrei salvarmi abbinandolo all'identificativo durante la sottoscrizione, ma non so se c'è anche un modo più elegante) var $sottoscrizioneAbbonamentoTriggerConfermato = false; var $restart_params = array("token"=>''); var $messaggioApplePayVerificaDisponibilita = "Stiamo verificando se il tuo sistema permette di effettuare pagamenti tramite ApplePay"; var $messaggioApplePayDisponibile = "Puoi effettuare il pagamento tramite ApplePay, clicca sul pulsante sotto per procedere con il pagamento."; var $messaggioApplePayNonDisponibile = "Non puoi effettuare il pagamento tramite ApplePay perché il tuo sistema non supporta questo pagamento."; var $applePayMerchantNation = "IT"; var $mode = 'paymentintents';//https://stripe.com/docs/payments/checkout/server //PSD2 READY (SCA): //"paymentElement" - pagamento sul sito con [link] - conferma sincrona - DA TESTARE LIVE //"paymentintents" (https://stripe.com/docs/payments/payment-intents/quickstart#manual-confirmation-flow) - pagamento sul sito - conferma sincrona //"paymentscheckout" (https://stripe.com/docs/payments/checkout/server) - pagamento su stripe - conferma asincrona (richiede creazione webhooks) //"savecard" (https://stripe.com/docs/payments/cards/saving-cards) //"sdd" //"applepay" //OLD: //"elements" (default) esperienza in-page //"checkout" (https://stripe.com/docs/checkout) popup che in più ha la funzione "rember me" per registrare la carta e pagare tramite codice di conferma sms var $payment_method_types = array("card");//klarna var $logo128x128 = null;//prima versione checkout (usata?) var $logo300x300 = null;//paymentscheckout sca ready var $valuta = 'eur'; var $lingua = "ita"; function setValuta($code){ //Three-letter ISO currency code, in lowercase. => https://www.iso.org/iso-4217-currency-codes.html $this->valuta = strtolower($code); } function demoInfo(){ $dev = "Pannello con credenziali API info@tnx.it/***********"; switch($this->mode){ case 'sdd': return " Coordinate bancarie per simulazione pagamento:
DE89370400440532013000: The charge status transitions from pending to succeeded
DE62370400440532013001: The charge status transitions from pending to failed
DE35370400440532013002: The charge succeeds but a dispute is immediately created ".$dev; case 'paymentscheckout': $dev .= "

SVILUPPATORI:
va creato un webhook nel backend (va inserita la chiave endpointSecretKey nella configurazione) con indirizzo \"".$this->Procedura->genera_link_agg(array($this->step_var=>"webhook", $this->Procedura->metodo_var=>$this->metodoprocedura_id))."\" e tipi evento \"charge.succeeded\" e \"charge.failed\""; case 'paymentElement': case 'paymentintents': case 'savecard': return " Carte per simulazione salvataggio dati:
4242424242424242 (carta standard)
4000002500003155 (autenticazione richiesta solo al primo uso)
4000002760003184 (autenticazione richiesta sempre, non caricabile off-session)
4000008260003178 (per simulare errore)
(cvv2 / scadenza qualsiasi, qui tutte le carte). ".$dev; case 'applepay': return $dev . "
Seguire questa guida per verifica dominio e inserimento del certificato su stripe"; default: return " Carte per simulazione pagamento:
4000003800000008 (italia)
4242424242424242 (usa, verifica cap)
4000000000009995 per simulare errore
(cvv2 / scadenza qualsiasi, qui tutte le carte). ".$dev; } } function ignoreSetupError(){ if(in_array($_GET[$this->step_var], array('webhook'))) return true; return false; } function handleWebhook(){ $this->pulisciOutput(); // mail("c@localhost", "Debug", __FILE__.":".__LINE__."\n".print_r(array($_REQUEST, $_SERVER, file_get_contents("php://input")), true)); require_once($GLOBALS['DATI']['libPath'].'/stripe/init.php'); \Stripe\Stripe::setApiKey($this->secretKey); // $this->endpointSecretKey = 'whsec_zn1aC84Unj7HNosX5FweiBobkRH4I8Yx'; // mail("c@localhost", "handleWebhook Stripe input", file_get_contents('php://input')); if(!$_SERVER['HTTP_STRIPE_SIGNATURE']){ $errore = "Richiesta non firmata"; } else if(!$this->endpointSecretKey){ $errore = "Secret key non configurata"; } else{ try { $event = \Stripe\Webhook::constructEvent( @file_get_contents('php://input'), $_SERVER['HTTP_STRIPE_SIGNATURE'], $this->endpointSecretKey ); } catch(\UnexpectedValueException $e){ $errore = "Invalid payload"; } catch(\Stripe\Error\SignatureVerification $e){ $errore = "Invalid signature"; } } if($errore){ trigger_error($errore); $this->Procedura->comunicazione_s2s($this->identificativo, $errore); http_response_code(400); die($errore); } else http_response_code(200); $debugInfo = "http".(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 's' : '')."://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']."\n".__FILE__.":".__LINE__."\n".print_r($event, true); if($event->data->object->metadata && property_exists($event->data->object->metadata, "identificativo_webhook")){ $identificativo_webhook = $event->data->object->metadata->identificativo_webhook; } else if(property_exists($event->data->object, "source") && $event->data->object->source->metadata && property_exists($event->data->object->source->metadata, "identificativo_webhook")){ //per gli abbonamenti sono riuscito a trovare il modo di passarlo solo sul source $identificativo_webhook = $event->data->object->source->metadata->identificativo_webhook; } else $identificativo_webhook = ''; // mail("c@localhost", get_class($event->data->object), __FILE__.":".__LINE__."\n".print_r(array($_REQUEST, $_SERVER), true)); if($identificativo_webhook){//se non c'è è un pagamento paymentintents e la s2s viene gestita diversamente if(in_array($event->type, array('charge.succeeded'))){ if($event->data->object->outcome->type != 'authorized'){ $this->Procedura->comunicazione_s2s($identificativo_webhook, $event->data->object->outcome->seller_message); $this->Procedura->comunicazione_s2s($identificativo_webhook, print_r($event->data->object, true)); trigger_error("Pagamento non autorizzato, probabile rischio frode avvertire il cliente: ".$event->data->object->outcome->seller_message); } else{ mail("c@localhost", "Controlla questo evento per inviare le notifiche al provve per i pagamenti falliti", $debugInfo); $this->Procedura->comunicazione_s2s($identificativo_webhook, print_r($event->data->object, true)); $this->Procedura->confermato($identificativo_webhook); } } else if($event->type == 'charge.failed') { $this->Procedura->comunicazione_s2s($identificativo_webhook, "Addebito fallito"); } else{ mail("c@localhost", "Evento Stripe imprevisto", $debugInfo); } } else if(get_class($event->data->object) == 'Stripe\Invoice'){ if(method_exists($this->Procedura, "ricorrenzaStripe")) $this->Procedura->ricorrenzaStripe($event); if(!in_array($event->type, [ 'invoice.created', 'invoice.deleted', // 'invoice.finalization_failed', 'invoice.finalized', // 'invoice.marked_uncollectible', // 'invoice.overdue', 'invoice.paid', // 'invoice.payment_action_required', // 'invoice.payment_failed', 'invoice.payment_succeeded', 'invoice.sent', 'invoice.upcoming', 'invoice.updated', 'invoice.voided', 'invoice.will_be_due', ])){ $mailSubj = ucfirst($event->type).(property_exists($event->data->object, "source")?" ".$event->data->object->source->owner->name . " " . $event->data->object->source->owner->email:""); } } else if(get_class($event->data->object) == 'Stripe\Dispute'){ //dispute (non hanno proprietà payment_method_details) $mailSubj = ucfirst($event->type)." ".$event->data->object->reason." (".$event->data->object->payment_intent.")"; } else if($event->data->object->payment_method_details->type == 'sepa_debit'){ //sepa di ordinalo, andranno gestiti sul sito quando verranno usati anche da altri if($event->type != 'charge.succeeded'){ $mailSubj = ucfirst($event->type).(property_exists($event->data->object, "source")?" ".$event->data->object->source->owner->name . " " . $event->data->object->source->owner->email:""); } } else{ // mail("c@localhost", "Questo dovrebbe essere un handleWebhook di un pagamento paymentintents (s2s gestita a parte)", $debugInfo); } // if($mailSubj) mail("carlo@tnx.it", $mailSubj, $debugInfo); if($mailSubj) mail("andrea@tnx.it", $mailSubj, $debugInfo); $this->pulisciOutput(); http_response_code(200); die(); } function indiLingua(){ switch($GLOBALS['DATI']["lang"]) { case "ita": $this->lingua = "ita"; break; case "fra": $this->lingua = "fra"; break; case "spa": $this->lingua = "spa"; break; case "ger": $this->lingua = "ted"; break; default: $this->lingua = "ing"; break; } } function placeholderIntestatario(){ return $this->lingua == 'ita' ? "Nome intestatario" : "Cardholder name"; } function rimborsa(){ $result = $this->stripeApiCall( "https://api.stripe.com/v1/refunds", array( "payment_intent" => $this->Procedura->infoRimborso['identificativo_pagamento'] ) ); if($result['amount']){ $this->Procedura->logga(array( 'identificativo_ordine' => $this->identificativo, 'ultimo_stato' => 'Rimborso preso in carico: '.($result['amount']/100).$result['currency'].' '.$result['status'].' ('.$result['id'].')' )); // $this->Procedura->logga(array( // 'identificativo_ordine' => $this->identificativo, // 'ultimo_stato' => 'Procedura di rimborso completata' // )); return 'ok';//tutto ok } else{ $this->Procedura->logga(array( 'identificativo_ordine' => $this->identificativo, 'ultimo_stato' => 'Errore durante il rimborso'."\n".print_r($result, true) )); return $result['error']['message']; } // Array // ( // [id] => re_1HSnBfERz1qJ9aShycQh0YiM // [object] => refund // [amount] => 1950 // [balance_transaction] => txn_1HSnBgERz1qJ9aShJpFqczEs // [charge] => ch_1HSmbYERz1qJ9aShB7VZifu0 // [created] => 1600449355 // [currency] => eur // [metadata] => Array // ( // ) // [payment_intent] => pi_1HSmbSERz1qJ9aShwtZAAGb1 // [reason] => // [receipt_number] => // [source_transfer_reversal] => // [status] => succeeded // [transfer_reversal] => // ) } function paymentintentsAjax(){ $this->pulisciOutput(); if($GLOBALS['DATI']['dove_sono'] == 'loc'){ $GLOBALS['_dev_email'] = "c@localhost"; $GLOBALS['CONF']['indi_error'] = "NO";//così mi arrivano almeno per email, altrimenti li perdo perchè l'output inizia e finisce in questa funzione // restore_error_handler();//se faccio così non scatta il "filtro" che ignora alcuni tipi di errori (ne stampa molti del tipo "Declaration of XXX should be compatible with") } $input = file_get_contents("php://input"); if(!$input) die;//su manjodelivery.it tutte le mattine arrivavano richieste in GET (da edge), forse un proxy, un virus, uno strano plugin // if(!$input){ //togliere tra qualche giorno di test 15/3/2021 // mail("c@localhost", "paymentintents Ajax senza POST data", __FILE__.":".__LINE__."\n".print_r(array($input, $_POST, $_GET, $_SERVER), true)); // die; // } $postedJson = json_decode($input); if($postedJson->removeCard){ $res = $this->stripeApiCall("https://api.stripe.com/v1/payment_methods/".$postedJson->removeCard."/detach"); //Returns a PaymentMethod object. echo json_encode([ "success" => isset($res['id']) ]); } else{ if($postedJson->payment_method_id){//richiedo l'addebito, potrebbe essere richiesta la conferma all'utente oppure no $payData = array( "payment_method" => $postedJson->payment_method_id, "amount" => round($this->importo*100), "description" => $this->descrizione, "currency" => $this->valuta, "confirmation_method" => "manual", "confirm" => "true", //necessario per nuove versioni api da 16/10/2023 https://stripe.com/docs/upgrades/manage-payment-methods 'payment_method_types' => ['card'] //alternativa: //'automatic_payment_methods' => ['enabled' => 'true', 'allow_redirects'=>'never'], //'confirmation_method' => null ); if($this->preAuth) $payData['capture_method'] = "manual";//preautorizzazione if($postedJson->customerId){ $payData['customer'] = $postedJson->customerId;//per utilizzo una carta salvata //attacco la carta al cliente esistente if($postedJson->saveCard){ $this->stripeApiCall( "https://api.stripe.com/v1/payment_methods/".$postedJson->payment_method_id."/attach", array( "customer" => $postedJson->customerId ) ); if($this->onNewCard) call_user_func($this->onNewCard, $this->identificativo, $postedJson->customerId, $postedJson->payment_method_id); } } else if($this->saveCustomer){//posso salvarlo anche senza salvare le carte $customerData = array( "email" => trim($this->cliente_email), "name" => $this->cliente_nome, "description" => $this->cliente_nome . ($this->cliente_riferimento ? " (".$this->cliente_riferimento.")" : ""),//viene esportato questo ); if($postedJson->saveCard){ $payData["setup_future_usage"] = $this->saveCard; $customerData['payment_method'] = $postedJson->payment_method_id; } $res = $this->stripeApiCall( "https://api.stripe.com/v1/customers", $customerData ); if($res['id']){ if($postedJson->saveCard){ if($this->onNewCard) call_user_func($this->onNewCard, $this->identificativo, $res['id'], $postedJson->payment_method_id); } $payData['customer'] = $res['id']; if(is_callable($this->saveCustomer) && call_user_func($this->saveCustomer, $this->identificativo, $res['id'])){ $this->Procedura->comunicazione_s2s($this->identificativo, "Cliente registrato: ".$res['id']); } } else if($res['error']){ //sembra che se durante l'abbinamento la carta venga validata (una carta senza fondi viene abbinata, quindi si parla di una carta invalida proprio) e possa quindi dare errore (lo' darà verosimilmente anche alla chiamata payment_intents più sotto) // esempio del 28/5/21: // "php://input" => {"payment_method_id":"pm_1Iw9TZKYMa4MmUtpL7ZRWtbC","saveCard":true,"customerId":""} // $customerData => Array // ( // [email] => imielzarek@gmail.com // [name] => ivan mielzare // [description] => ivan mielzare // [payment_method] => pm_1Iw9TZKYMa4MmUtpL7ZRWtbC // ) // $res => Array // ( // [code] => card_declined // [decline_code] => do_not_honor // [doc_url] => https://stripe.com/docs/error-codes/card-declined // [message] => Your card was declined. // [param] => // [type] => card_error // ) } else{ mail( "c@localhost", "Cliente Stripe non registrato controllare", __FILE__.":".__LINE__."\n".print_r(array(file_get_contents("php://input"), $customerData, $res), true) ); } } $intent = $this->stripeApiCall( "https://api.stripe.com/v1/payment_intents", $payData ); // mail("c@localhost", "Oggetto", wordwrap(print_r($intent, true), 70, "\r\n"), "MIME-Version: 1.0\r\nContent-type: text/html; charset=utf-8\r\n"); if($intent['error_tnx'] == 'auth'){ echo json_encode(['error' => $this->authErrorText]); die; } } else if($postedJson->payment_intent_id){//il pagamento ha richiesto una conferma PSD2/SCA (response.requires_action) $intent = $this->stripeApiCall( "https://api.stripe.com/v1/payment_intents/".$postedJson->payment_intent_id."/confirm" ); } //On versions of the API before 2019-02-11, requires_payment_method appears as requires_source and requires_action appears as requires_source_action. if(in_array($intent["status"], array('requires_action', 'requires_source_action')) && $intent["next_action"]['type'] == 'use_stripe_sdk') { # Tell the client to handle the action echo json_encode([ 'requires_action' => true, 'payment_intent_client_secret' => $intent['client_secret'] ]); } else if ($intent["status"] == 'succeeded' || ($this->preAuth && $intent["status"] == 'requires_capture')) { # The payment didn’t need any additional actions and completed! # Handle post-payment fulfillment //$payment_intent_id = $postedJson->payment_intent_id c'è in test, ma non sempre live (sirio_verona) $payment_intent_id = $intent['id']; if($postedJson->savedCard && $this->onSavedCardUse) call_user_func($this->onSavedCardUse); $this->Procedura->comunicazione_s2s($this->identificativo, "Importo ricevuto: ".number_format($intent['amount']/100, 2, ",", "")."€ (ID: ".$payment_intent_id.")" // ."cliente: ".$res['source']['name'].", " // ."ID: ".$res['balance_transaction'] ); // mail("c@localhost", "Debug Stripe", __FILE__.":".__LINE__."\n".print_r(array($postedJson, $intent, $_SERVER), true)); $this->Procedura->confermato($this->identificativo, null, $payment_intent_id); echo json_encode([ "success" => true ]); } else if ($intent["error"]) { if($intent["error"]['code'] == 'card_decline_rate_limit_exceeded') $message = 'Troppi tentativi con questa carta, riprova tra 24 ore'; else $message = $intent['error']["message"]; $this->Procedura->comunicazione_s2s($this->identificativo, "Errore inviato al cliente: ".$message ); echo json_encode(['error' => $message]); } else { //successo una volta durante pagamento test terredelbruno + chefawayvilladarte mail("c@localhost", "Stripe Invalid PaymentIntent status", __FILE__.":".__LINE__."\n".print_r(array(file_get_contents("php://input"), $postedJson, $intent, $_POST, $_GET, $_SERVER), true)); # Invalid status http_response_code(500); echo json_encode(['error' => 'Invalid PaymentIntent status']); } } die; } function paymentElementHtml(){ $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; $payData = array( "payment_method_types" => ["card","link"], "amount" => round($this->importo*100), "description" => $this->descrizione, "currency" => $this->valuta, "metadata" => [ 'identificativo_webhook' => $this->identificativo, ] ); $intent = $this->stripeApiCall( "https://api.stripe.com/v1/payment_intents", $payData ); $return = "
Procedura->classeDivContainer."\">"; $return .= ($this->importo?$this->Procedura->riepilogoPagamentoHtml():$this->saveCustomerOnlyIntroHtml); $return .= $di; $return .= "
".$this->loadingImg."
"; $return .= "
"; $return .= "
"; return $return; } function paymentintentsHtml(){ // if($_GET['testtnx']) return $this->paymentElementHtml(); $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; $return = "
Procedura->classeDivContainer."\">"; $return .= ($this->importo?$this->Procedura->riepilogoPagamentoHtml():$this->saveCustomerOnlyIntroHtml); $return .= $di; $return .= "
".$this->loadingImg."
"; $return .= "
"; $ciSonoCarteSalvate = false; $carteSalvabili = $this->getCustomer && $this->saveCustomer && is_callable($this->getCustomer) && is_callable($this->saveCustomer); if($carteSalvabili && $customerId = call_user_func($this->getCustomer)){ $res = $this->stripeApiCall( "https://api.stripe.com/v1/payment_methods", ['customer' => $customerId, 'type' => 'card'], "GET" ); // mail("c@localhost", "Oggetto", wordwrap(print_r($res), 70, "\r\n"), "MIME-Version: 1.0\r\nContent-type: text/html; charset=utf-8\r\n"); if($res['data']){ $ciSonoCarteSalvate = true; $return .= '
'; // var $testo_procedi_con_altra_carta = "Procedi con un'altra carta"; foreach($res['data'] as $c){ $return .= '
'; $return .= ''.implode(",", array_map(ucfirst, $c['card']['networks']['available'])).''; $return .= '**** **** **** '.$c['card']['last4'].''; $return .= ''.$c['card']['exp_month'].'/'.$c['card']['exp_year'].''; $return .= ''.$this->Procedura->htmlentities($this->Procedura->testo_utilizza_carta_salvata).''; $return .= ''.$this->Procedura->htmlentities($this->Procedura->testo_rimuovi_carta_salvata).''; $return .= '
'; } $return .= ''; $return .= '
'; } } if($ciSonoCarteSalvate){ $return .= '
'; $return .= ''; } $return .= "
". //placeholderIntestatario()."\" type=\"text\"class=\"StripeElement\"> "
"; if($carteSalvabili) $return .= '
'; $return .= "
"; if($ciSonoCarteSalvate) $return .= '
'; $return .= " "; return $return; } function savecardHtml(){ $res = $this->stripeApiCall( "https://api.stripe.com/v1/setup_intents", array( // "usage" => "off_session",//è così di default ) ); if($res['error_tnx'] == 'auth'){ return $this->Procedura->errore($this->authErrorText); } $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; return "
Procedura->classeDivContainer."\">". ($this->importo?$this->Procedura->riepilogoPagamentoHtml():$this->saveCustomerOnlyIntroHtml). $di. "
placeholderIntestatario()."\" type=\"text\"class=\"StripeElement\">
".$this->loadingImg."
"; } function paymentscheckoutHtml(){ $params = array( "payment_method_types" => $this->payment_method_types, // "automatic_payment_methods[enabled]" => true, // "automatic_payment_methods[allow_redirects]" => 'never', // "" => , "payment_intent_data[metadata][identificativo_webhook]" => $this->identificativo, "success_url" => $this->Procedura->genera_link_agg(array($this->step_var=>"return_ok")), "cancel_url" => $this->Procedura->genera_link_agg(array($this->step_var=>"return_ko")), // "payment_intent_data[capture_method]" => 'manual', // "client_reference_id" => $this->identificativo, // vecchia versione prima di prices api (https://stripe.com/docs/payments/checkout/migrating-prices) // "payment_intent_data[description]" => $this->descrizione, // "line_items[][name]" => $this->descrizione, // "line_items[][amount]" => round($this->importo*100), // "line_items[][currency]" => $this->valuta, // "line_items[][quantity]" => '1', // "line_items[][images][]" => $this->logo300x300, "mode" => "payment", "payment_intent_data[description]" => $this->descrizione, "line_items[][quantity]" => '1', "line_items[][price_data][unit_amount]" => round($this->importo*100), "line_items[][price_data][currency]" => $this->valuta, "line_items[][price_data][product_data][name]" => $this->descrizione, "line_items[][price_data][product_data][images][]" => $this->logo300x300, ); if($this->cliente_email) $params['customer_email'] = $this->cliente_email;//se vuoto da' errore $res = $this->stripeApiCall("https://api.stripe.com/v1/checkout/sessions", $params); if($res['error_tnx'] == 'auth') return $this->Procedura->errore($this->authErrorText); else if(!$res['id']) trigger_error("Errore inizializzazione stripe ".print_r($res, true)); else{ return " "; } } function applepayHtml(){ $html = '

' . $this->messaggioApplePayVerificaDisponibilita . '

'; return $html; } function sddInit(){ $this->pulisciOutput(); if($GLOBALS['DATI']['dove_sono'] == 'loc'){ $GLOBALS['_dev_email'] = "c@localhost"; $GLOBALS['CONF']['indi_error'] = "NO";//così mi arrivano almeno per email, altrimenti li perdo perchè l'output inizia e finisce in questa funzione // restore_error_handler();//se faccio così non scatta il "filtro" che ignora alcuni tipi di errori (ne stampa molti del tipo "Declaration of XXX should be compatible with") } $input = file_get_contents("php://input"); $postedJson = json_decode($input, true); $cus = $this->stripeApiCall("https://api.stripe.com/v1/customers",[ "email" => $postedJson['email'], "name" => $postedJson['name'], "description" => $postedJson['name'] . ($this->cliente_riferimento ? " (".$this->cliente_riferimento.")" : ""),//viene esportato questo ]); $si = $this->stripeApiCall( "https://api.stripe.com/v1/setup_intents", array( "customer" => $cus['id'], "payment_method_types[]" => "sepa_debit", ) ); if($si['error_tnx'] == 'auth') $json = [ 'error' => $this->authErrorText ]; else $json = [ 'customer_id' => $cus['id'], 'client_secret' => $si['client_secret'] ]; echo json_encode($json); die; } function sddHtml(){ $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; $intro = ''; if($this->mode=='sdd') $intro = $this->Procedura->riepilogoPagamentoHtml().$this->sddIntroHtml; else if($this->importo) $intro = $this->Procedura->riepilogoPagamentoHtml(); else $intro = $this->saveCustomerOnlyIntroHtml; return "
Procedura->classeDivContainer."\">". $intro. $di. "
".(!$this->cliente_estero?"":"
")."
".$this->Procedura->htmlentities(str_replace("##BENEFICIARIO##", $this->sddBeneficiario, $this->sddAccettazione))."
".$this->loadingImg."
"; } function sddHtmlOLD(){ $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; $intro = ''; if($this->mode=='sdd') $intro = $this->Procedura->riepilogoPagamentoHtml().$this->sddIntroHtml; else if($this->importo) $intro = $this->Procedura->riepilogoPagamentoHtml(); else $intro = $this->saveCustomerOnlyIntroHtml; return "
Procedura->classeDivContainer."\">". $intro. $di. "
".(!$this->cliente_estero?"":"
")."
".$this->Procedura->htmlentities(str_replace("##BENEFICIARIO##", $this->sddBeneficiario, $this->sddAccettazione))."
".$this->loadingImg."
"; } function elementsHtml(){ //https://stripe.com/docs/stripe-js $di = $this->demo ? $this->demoInfo() : ''; if($di) $di = '
'.$di.'
'; return "
Procedura->classeDivContainer."\">". ($this->importo?$this->Procedura->riepilogoPagamentoHtml():$this->saveCustomerOnlyIntroHtml). $di. "
".$this->loadingImg."
"; } function checkoutHtml(){ //https://stripe.com/docs/checkout#integration return "
".$this->loadingImg."
"; } function updateSubscription($subscriptionItemId, $priceData, $proration_behavior = 'none' /*always_invoice (fattura subito), create_prorations (aggiungi alla prossima)*/){ // $price = $this->stripeApiCall("https://api.stripe.com/v1/prices", $priceData); $res = $this->stripeApiCall("https://api.stripe.com/v1/subscription_items/".$subscriptionItemId, [ // 'price' => $price['id'], 'price_data' => $priceData, 'proration_behavior' => $proration_behavior ]); return $res['id']; } function billingPortalLink($customerId){ $ret = $this->stripeApiCall("https://api.stripe.com/v1/billing_portal/sessions", ["customer"=>$customerId]); // [id] => bps_1RiyhMERz1qJ9aSh84DlEgpR // [object] => billing_portal.session // [configuration] => bpc_1RiyhIERz1qJ9aSh8PTGOwpJ // [created] => 1752070212 // [customer] => cus_SdS9JfMabvM4bw // [flow] => // [livemode] => // [locale] => // [on_behalf_of] => // [return_url] => // [url] => https://billing.stripe.com/p/session/test_YWNjdF8xOFF3b1JFUnoxcUo5YVNoLF9TZUhGdkdYaGxGcmlmRkVPT081OWpEbnJLWnB6bW1O0100yTPoSLSu return $ret['url']; } function stripeApiCall($url, $data = [], $method = 'POST'){ $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_USERPWD, $this->secretKey.":"); //deve essere sempre chiamato, perchè setta il content if($method == 'POST'){ curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); } // else curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-length: 0']);//di default è Content-Length: -1 e la chiamata API va in errore!!! così funziona ma per sicurezza setto sempre CURLOPT_POSTFIELDS a array vuota else{//GET // curl_setopt($ch, CURLOPT_HTTPGET, true); // curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); $url .= "?".http_build_query($data); } curl_setopt($ch, CURLOPT_URL, $url); // curl_setopt($ch, CURLOPT_STDERR, fopen("/tnx/www/html/www/indi/INDI_ver1.1/_lib/pagamenti/log", 'w')); // curl_setopt($ch, CURLOPT_VERBOSE, true); // else curl_setopt($ch, CURLOPT_POSTFIELDS, false); $res = json_decode(curl_exec($ch), true); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 200 - OK Everything worked as expected. // 400 - Bad Request The request was unacceptable, often due to missing a required parameter. // 401 - Unauthorized No valid API key provided. // 402 - Request Failed The parameters were valid but the request failed. // 404 - Not Found The requested resource doesn't exist. // 409 - Conflict The request conflicts with another request (perhaps due to using the same idempotent key). // 429 - Too Many Requests Too many requests hit the API too quickly. We recommend an exponential backoff of your requests. // 500, 502, 503, 504 - Server Errors Something went wrong on Stripe's end. (These are rare.) curl_close($ch); if($code == 401 || strpos($res['error']['message'], 'Invalid API Key provided') === 0){ if($code != 401) trigger_error("controllare response code $code");//altrimenti togliere l'or qui sopra $this->Procedura->comunicazione_s2s($this->identificativo, $this->authErrorText); $res['error_tnx'] = 'auth'; } else if(in_array($res['error']['code'], ['card_decline_rate_limit_exceeded'])){ //gestisco } else if($res['error']['code'] == 'resource_missing' && $res['error']['param'] == 'customer'){ //cliente cancellato da stripe (anche multiattività che condivide gli utenti, ma non i metodi di pagamento) //non compariranno le carte di credito salvate } else if(in_array($res['error']['type'], ['invalid_request_error', 'token_already_used']) && !in_array($res['error']['code'], ['card-decline-rate-limit-exceeded', 'charge_already_refunded'])){ //probabile errore nostro //in caso di The payment method you provided has already been attached to a customer //è successo che a distanza di 2 giorni degli ip amazonws visitassero le stesse pagine del cliente reale (stesso token) generando l'errore trigger_error("Richiesta non valida fatta a Stripe (integrazione da rivedere): ".print_r($res, true)); } return $res; } function auto(){ if(function_exists("mod_pc_salva_servizio")) mod_pc_salva_servizio("stripe"); if($this->demo){ $this->publishableKey = 'pk_test_0Q5pEQeV0Oa187StXnTbtC1p'; // $this->secretKey = 'sk_test_0K1oj0jtZiDKbRANQidaC1jM'; $this->secretKey = 'rk_test_518QwoRERz1qJ9aShiCEgbcz1qfubpqQzA59khNlv8T2tLKjAHtOJTf3jOoFFipdHfsoCgp9GBYEMUllinzaqn08d00pybYinfw'; } if($this->saveCard && !$this->saveCustomer) $this->saveCustomer = true;//necessario https://stripe.com/docs/payments/save-during-payment if($this->abbonamento || $this->mode == 'savecard') $this->importo = 0;//nella pagina di test posso lasciare l'importo settato if(!$this->publishableKey || !$this->secretKey) return "
Procedura->classeDivContainer."\">

Parametri mancanti
"; // $this->Procedura->logga(array( // 'identificativo_ordine' => $this->identificativo, // 'ultimo_stato' => 'Fine procedura di conferma' // )); if($this->Procedura->rimborso) return $this->rimborsa(); else{ if($this->importo && $this->importo < 0.5) return $this->Procedura->errore("Non accettiamo pagamenti con carta inferiori a 0,50€"); switch($_GET[$this->step_var]){ default: if(in_array($this->mode, ["checkout", "elements"])) trigger_error("Stripe mode=".$this->mode." non è conforme alla normativa PSD2 (SCA)"); $url = $this->Procedura->genera_link_agg(array($this->step_var=>"return_ok")); $this->Procedura->iniziato($this->nome_metodo); return $this->{$this->mode."Html"}(); case 'non_supportato': if($this->messaggioApplePayNonDisponibile) $this->Procedura->testo_non_supportato = $this->messaggioApplePayNonDisponibile; return $this->Procedura->errore($this->Procedura->testo_non_supportato); case 'sddInit': return $this->sddInit(); break; case 'ajax': return $this->{$this->mode."Ajax"}();//paymentintentsAjax break; case 'return_ok'://solo risposta all'utente qui, le operazioni in s2s if(!$this->importo) $this->Procedura->testo_concluso = $this->saveCustomerOnlyEndText; return $this->Procedura->concluso(); case 'return_ko'://solo risposta all'utente qui, le operazioni in s2s return $this->Procedura->annulla(); case 'error': return $this->Procedura->errore($this->errore_mancata_s2s); case 'creaPiano': die("si possono creare anche dalla dashboard (billing > prodotti > crea prodotto > crea piano > copiare l'id del **piano**"); $nomePiano = 'Ordinalo test'; //è necessario un prodtto per abbinarci un piano $res = $this->stripeApiCall( "https://api.stripe.com/v1/products", array( "name" => $nomePiano, "type"=>"service" ) ); if(!$res['id']){ echo '
Errore creazione prodotto:';
						// $debugBacktrace = debug_backtrace(); array_unshift($debugBacktrace, array('file'=>__FILE__, 'line'=>__LINE__, 'function'=>'debugTnx')); foreach($debugBacktrace as $debugLine) echo "".str_replace("/tnx/www/html/www/", "", $debugLine['file']).""." ".$debugLine['function']."()
"; $printMe = $res; ob_start(); if(is_object($printMe)||is_array($printMe)) print_r($printMe); else var_dump($printMe); echo htmlentities(ob_get_clean(), ENT_COMPAT|ENT_HTML401|ENT_SUBSTITUTE, 'UTF-8'); echo '
'; die; } //https://stripe.com/docs/api/plans $res = $this->stripeApiCall( "https://api.stripe.com/v1/plans", array( "product" => $res['id'], "nickname"=>$nomePiano, "interval"=>'month', "currency"=>$this->valuta, "amount"=> 89 * 100, ) ); echo '
Piano creato:';
					// $debugBacktrace = debug_backtrace(); array_unshift($debugBacktrace, array('file'=>__FILE__, 'line'=>__LINE__, 'function'=>'debugTnx')); foreach($debugBacktrace as $debugLine) echo "".str_replace("/tnx/www/html/www/", "", $debugLine['file']).""." ".$debugLine['function']."()
"; $printMe = $res; ob_start(); if(is_object($printMe)||is_array($printMe)) print_r($printMe); else var_dump($printMe); echo htmlentities(ob_get_clean(), ENT_COMPAT|ENT_HTML401|ENT_SUBSTITUTE, 'UTF-8'); echo '
'; die; case 'webhook': return $this->handleWebhook(); case 'verify': $data = $this->stripeApiCall("https://api.stripe.com/v1/payment_intents/".$_GET['payment_intent']); if($data["status"] != 'succeeded'){ trigger_error($data['last_payment_error']); $this->Procedura->comunicazione_s2s($this->identificativo, "Errore: ".$data['last_payment_error']); $redirectStep = "error"; } else{ $redirectStep = "return_ok"; $this->Procedura->comunicazione_s2s($data["metadata"]["identificativo_webhook"], "Importo ricevuto: ".($data["amount_received"]/100).$data["currency"]); $this->Procedura->confermato($data["metadata"]["identificativo_webhook"]); } $this->Procedura->redirect($this->Procedura->genera_link_agg(array("payment_intent_client_secret"=>"","redirect_status"=>"","payment_intent"=>"",$this->step_var=>$redirectStep))); return; case 's2s': //se rubo un token non lo posso utilizzare più di una volta //se cambio l'url per la s2s semplicemente pago l'ordine di un altro perchè l'importo autorizzato non è vincolante $identificativo = $this->identificativo; $procediAddebitoImporto = true; if($this->saveCustomer || $this->abbonamento){ if(!$_GET['customer_id']){//in questo caso è creato da sddInit $nome = $this->cliente_nome; $data = array( "email" => $this->cliente_email, "name" => $nome, "description" => $nome . ($this->cliente_riferimento ? " (".$this->cliente_riferimento.")" : ""),//viene esportato questo ); } $url = "https://api.stripe.com/v1/customers"; if($_GET['customer_id']){ $customerId = $_GET['customer_id']; $url .= "/" . $customerId; $data['invoice_settings']['default_payment_method'] = $_GET['pM']; } else{ $data['payment_method'] = $_GET['token']; if($this->abbonamento) $data['invoice_settings']['default_payment_method'] = $_GET['token']; } $res = $this->stripeApiCall( $url, $data ); if(!$_GET['customer_id']) $customerId = $res['id']; // mail("a.toce@tnx.it", "Debug", __FILE__.":".__LINE__."\n".print_r(array($res, $_REQUEST, $_SERVER), true)); // mail("c@localhost", "Debug", __FILE__.":".__LINE__."\n".print_r(array($res, $_REQUEST, $_SERVER), true)); if($res['error_tnx'] == 'auth'){ return $this->Procedura->errore($this->authErrorText); } else if($res['error'] && $err = $res['error']['message']){ $this->Procedura->comunicazione_s2s($identificativo, "Error: ".$err); return $this->Procedura->errore($err); } else if($customerId){ if(is_callable($this->saveCustomer)){ if(call_user_func($this->saveCustomer, $identificativo, $customerId)){ $this->Procedura->comunicazione_s2s($identificativo, "Cliente registrato: ".$customerId); $redirectStep = "return_ok"; } else{ $this->Procedura->comunicazione_s2s($identificativo, "Errore: ".print_r($res, true)); $procediAddebitoImporto = false; $redirectStep = "error"; } } else if($this->mode == 'savecard'){ $msg = "Non è stato impostato il metodo saveCustomer"; $this->Procedura->comunicazione_s2s($identificativo, $msg); trigger_error($msg); $redirectStep = "error"; } else $this->Procedura->comunicazione_s2s($identificativo, "Cliente Stripe: ".$customerId); if($this->abbonamento){ if(is_array($this->abbonamento)){ $abbonamenti = $this->abbonamento; } else $abbonamenti = [['plan' => $this->abbonamento]]; $allOk = true; foreach($abbonamenti as $k=>$callData){ $callData["customer"] = $customerId; $resSub = $this->stripeApiCall("https://api.stripe.com/v1/subscriptions", $callData); $nome = "abbonamento ".(is_string($this->abbonamento)?$this->abbonamento:($k+1)); if(in_array($resSub['status'], ['active', 'trialing'])){ $this->Procedura->comunicazione_s2s($identificativo, ucfirst($nome)." sottoscritto: ".$resSub['id']); } else{ $this->Procedura->comunicazione_s2s($identificativo, "Errore $nome: ".print_r($resSub, true)); $allOk = false; } } if($allOk){ $this->Procedura->confermato($identificativo, $resSub); $redirectStep = "return_ok"; } else{ $procediAddebitoImporto = false; $redirectStep = "error"; } } } } if($this->importo && $procediAddebitoImporto){ $data = array( "amount" => round($this->importo*100), "description" => $this->descrizione, "currency" => $this->valuta, ); if($_GET['sdd']){//source e charge per sdd sono stati deprecati $data['customer'] = $customerId; $data['payment_method'] = $_GET['pM']; $data['off_session'] = 'true'; $data['confirm'] = 'true'; $data['payment_method_types[]'] = 'sepa_debit'; // print_r_tnx($data, $_SERVER['REMOTE_ADDR'] == '192.168.0.177') || die; $pi = $this->stripeApiCall( "https://api.stripe.com/v1/payment_intents", $data ); if($pi['last_payment_error']){ trigger_error($pi['last_payment_error']); $this->Procedura->comunicazione_s2s($identificativo, "Errore: ".$pi['last_payment_error']); $redirectStep = "error"; } else{ $redirectStep = "return_ok"; $this->Procedura->comunicazione_s2s($identificativo, "Addebito SEPA avviato"); $this->Procedura->confermato($identificativo); } } else{ if(!$customerId) $data['source'] = $_GET['token']; else $data['customer'] = $customerId; $res = $this->stripeApiCall( "https://api.stripe.com/v1/charges", $data ); // mail("a.toce@tnx.it", "Debug", __FILE__.":".__LINE__."\n".print_r(array($res, $_REQUEST, $_SERVER), true)); if($res['error']['type'] == 'invalid_request_error'){//errore nostro $redirectStep = "error"; } else if($res['object'] != 'charge' || ($this->mode != 'sdd' /* sdd non è immediatamente paid */ && !$res['paid'])){ $this->Procedura->comunicazione_s2s($identificativo, "Errore: ".print_r($res, true)); $redirectStep = "error"; } else{ //https://stripe.com/docs/api/curl#charge_object if($res['fraud_details']){ $msg = "Dettagli frode ricevuti da Stripe: ".implode(", ", $res['fraud_details']); $this->Procedura->comunicazione_s2s($identificativo, $msg); trigger_error($msg); } if($res['outcome']['type'] != 'authorized'){ $msg = "Pagamento accettato, ma rischio frode segnalato da Stripe: ".$res['outcome']['seller_message']." (".$res['outcome']['reason'].")"; $this->Procedura->comunicazione_s2s($identificativo, $msg); trigger_error($msg); } $this->Procedura->comunicazione_s2s($identificativo, print_r($res, true));// $res['balance_transaction'] non c'è sempre (vedi cliente svizzero amato.vito86@gmail.com) $this->Procedura->comunicazione_s2s($identificativo, "Importo ricevuto: ".number_format($res['amount']/100, 2, ",", "")."€, ". "cliente: ".$res['source']['name'].", ". "ID: ".$res['balance_transaction'] ); $this->Procedura->confermato($identificativo); $redirectStep = "return_ok"; } } } // echo '
';
					// $printMe = $res; if(is_object($printMe)||is_array($printMe)) print_r($printMe); else var_dump($printMe);
					// echo '
'; // die; $this->Procedura->redirect($this->Procedura->genera_link_agg(array("token"=>"",$this->step_var=>$redirectStep))); break; } } } // [id] => ch_1BvOdAERz1qJ9aShdYIaq8yb // [object] => charge // [amount] => 999 // [amount_refunded] => 0 // [application] => // [application_fee] => // [balance_transaction] => txn_1BvOdAERz1qJ9aShsNEq19qS // [captured] => 1 // [created] => 1518608456 // [currency] => eur // [customer] => // [description] => // [destination] => // [dispute] => // [failure_code] => // [failure_message] => // [fraud_details] => Array // ( // ) // [invoice] => // [livemode] => // [metadata] => Array // ( // ) // [on_behalf_of] => // [order] => // [outcome] => Array // ( // [network_status] => approved_by_network // [reason] => // [risk_level] => normal // [seller_message] => Payment complete. // [type] => authorized // ) // [paid] => 1 // [receipt_email] => // [receipt_number] => // [refunded] => // [refunds] => Array // ( // [object] => list // [data] => Array // ( // ) // [has_more] => // [total_count] => 0 // [url] => /v1/charges/ch_1BvOdAERz1qJ9aShdYIaq8yb/refunds // ) // [review] => // [shipping] => // [source] => Array // ( // [id] => card_1BvOd6ERz1qJ9aShbh9PpTMb // [object] => card // [address_city] => // [address_country] => // [address_line1] => // [address_line1_check] => // [address_line2] => // [address_state] => // [address_zip] => // [address_zip_check] => // [brand] => Visa // [country] => US // [customer] => // [cvc_check] => pass // [dynamic_last4] => // [exp_month] => 12 // [exp_year] => 2019 // [fingerprint] => hdfRV57FLixgF2J0 // [funding] => credit // [last4] => 4242 // [metadata] => Array // ( // ) // [name] => carlo@tnx.it // [tokenization_method] => // ) // [source_transfer] => // [statement_descriptor] => // [status] => succeeded // [transfer_group] => } ?>