It depends on what you want. For a paying service only, I suggest you too to use the paypal JS. If you want something more structured think about realizing payment module that manage payment methods and store payments.
In my case I've used plugins inside a payment module to attacch different payment methods in the same code, storing the results and other useful data.
That’s my solution for paypal without all the module part:
In your controller:
public function executePaypalAction(){
try{
//recover the values from the DB. I have a paymentComponent that do it. I’ve cutted its initialization
foreach($paymentComponent->payment->getParameters() as $k => $v) $$k=$v;
//assign the values to the view.
$this->view->setVar('formaction', $paypal_url);
$this->view->setVar('paypal_merchant', $paypal_merchant);
//YOU NEED AN IPN NOTIFY ACTION CONTROLLER
$this->view->setVar('notify_url', 'https://'.$_SERVER['HTTP_HOST'].'/payment/paypal/paypalipn');
$this->view->setVar('returnUrl', $returnURL);
$this->view->setVar('language', $language);
$this->view->setVar('custom', $this->payment->getPaymentCode());
$this->view->setVar('ora', $ora);
$this->view->setVar('data', $data);
$this->view->setVar('descrizione', $descrizione);
$this->view->setVar('importo', $amount);
$this->view->setVar('id_order', $orderCode);
$this->view->setVar('id', $portal.'-'.$orderCode);
}
catch (\Exception $e) {
echo $e->getMessage() . '<br>';
echo '<pre>' . $e->getTraceAsString() . '</pre>';
}
$this->dispatcher->forward([
"namespace" => "App\Payment\Controllers",
"controller" => "paypal",
"action" => "index",
]);
}
The index.volt for paypal:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<script src="/javascripts/jquery/jquery-3.1.1.min.js"></script>
<meta http-equiv="cache-control" content="no-cache" />
<script type="text/javascript">
$(function(){
$('#submit').click();
});
</script>
</head>
<body>
<form action="{{formaction}}" method="post" id='myform'>
<!-- Identify your business so that you can collect the payments. -->
<input type="hidden" name="business" value="{{paypal_merchant}}">
<!-- Specify a Buy Now button. -->
<input type="hidden" name="cmd" value="_xclick">
<!-- Specify details about the item that buyers will purchase. -->
<input type="hidden" name="item_name" value="{{descrizione}}">
<input type="hidden" name="item_number" value="{{id}}">
<input type="hidden" name="lang" value="{{language}}">
<input type="hidden" name="custom" value="{{custom}}">
<input type="hidden" name="amount" value="{{importo}}">
<input type="hidden" name="currency_code" value="EUR">
<input type="hidden" name="notify_url" value="{{notify_url}}">
<input type="hidden" name="return" value="{{returnUrl}}">
<input type="hidden" name="no_shipping" value="1">
<input type="submit" name="submit" border="0" id="submit" value="Invia" style="display:none;">
</form>
</body>
</html>
The IPN controller action:
public function paypalipnAction()
{
$this->view->disable();
$this->logger->info("Paypal ipn start");
$logmessage='';
// impostiamo nella request il 'cmd'
$req = 'cmd=_notify-validate';
//controlla se ci sono o meno i magic_quotes
if(function_exists('get_magic_quotes_gpc')) {
$get_magic_quotes_exists = true;
}
//cicla tutti i valori passati in post e li riallega alla chiamata
foreach ($this->request->getPost() as $key => $value) {
if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
$value = urlencode(stripslashes($value));
} else {
$value = urlencode($value);
}
$req .= "&$key=$value";
}
// Post IPN data back to PayPal to validate the IPN data is genuine
// Without this step anyone can fake IPN data
//questo environment andrebbe ripreso dal payment
$environment='';
if($environment == 'sandbox') {
$paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
} else {
$paypal_url = "https://www.paypal.com/cgi-bin/webscr";
}
$ch = curl_init();
if ($ch == FALSE) {
return FALSE;
}
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt ($ch, CURLOPT_URL, $paypal_url);
$res = curl_exec($ch);
if (curl_errno($ch) != 0) // cURL error
{
$error.=date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL;
curl_close($ch);
exit;
} else {
// Log the entire HTTP response if debug is switched on.
$logmessage.=date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL;
$logmessage.=date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL;
curl_close($ch);
}
// Inspect IPN validation result and act accordingly
// Split response headers and payload, a better way for strcmp
$tokens = explode("\r\n\r\n", trim($res));
$res = trim(end($tokens));
$logmessage.="PAYPALIPN per ".$this->request->getPost('item_number')." - Status: ".$this->request->getPost('payment_status')." - res: ".$res;
if (strcmp ($res, "VERIFIED") == 0) {
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN POST: ".print_r($this->request->getPost(),1) . PHP_EOL;
// check whether the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your PayPal email
// check that payment_amount/payment_currency are correct
// process payment and mark item as paid.
// assign posted variables to local variables
//$item_name = $_POST['item_name'];
$item_number = $this->request->get('item_number');
$payment_status = $this->request->get('payment_status');
//$payment_amount = $_POST['mc_gross'];
//$payment_currency = $_POST['mc_currency'];
//$txn_id = $_POST['txn_id'];
//$receiver_email = $_POST['receiver_email'];
//$payer_email = $_POST['payer_email'];
$aItemNumber=explode('-',$this->request->get('item_number'));
$order_id=array_pop($aItemNumber);
//forse il portale sarebbe da recuperare in altro modo direttamente dal payment
$portal=implode('-',$aItemNumber);
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN POST: ".$portal . PHP_EOL;
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN POST: ".$order_id . PHP_EOL;
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN POST: ".$portal . PHP_EOL;
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN POST: ".$order_id . PHP_EOL;
// $paymentStatus is part of my payment model and is used to track the payment during his flow
$payment = Payment::findFirst([
'conditions' => 'paymentCode = ?0',
'bind' => [$paymentCode],
]);
$paymentStatus=$payment->getStatus();
if($paymentStatus == 'FORWARDED') { //check that the payment has not been processed before
if($payment_status=="Completed"){
//payment ok
$logmessage.=date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL;
//action to se status to ACCEPTED
$payment=Payment::confirm($paymentCode);
$logmessage.=date('[Y-m-d H:i e] '). "Paypal: order #$order_id completed" . PHP_EOL;
}else{
// pagamento fallito
//settare pagamento REJECTED
$payment=Payment::reject($paymentCode);
$logmessage.='Pagamento respinto';
}
exit;
}else{
//nothing to do.
exit;
}
} else if (strcmp ($res, "INVALID") == 0) {
//settare il pagamento su error
$logmessage.='Errore nel sistema';
exit;
$filedest = 'pos-receiveko.php';
}
}
}