cakephp PayPal IPN Integration - cakephp

I am using CakePHP 1.3.10 version and want to integrate PayPal IPN for Payment Process.
I have found some ready made plug-ins though not working properly and returning bunch of errors.
I would like your suggestions, Any body in community using the same with success and any tutorial to integrate in easy steps.
Your response would be appreciated.
Thanks !

I just discovered a nice PHP class, that runs all the PayPal IPN.
https://github.com/Quixotix/PHP-PayPal-IPN/
I turned it into a Component for my CakePhp project.
For this just create a new Component in you app/Controller/Components/ folder and paste the code from that project.
Then Change:
class IpnListener {
...
to
class IpnListener extends Component {
...
Then go back to the controller you want to you PayPal Ipn with and add:
public $components = array('IpnListener');
You can than access the class using:
$this->IpnListener->foo
within your controller functions
Hope this helps

I used Paypal IPN with cake before, and it's simple enough to not have to reply on a plugin. Are you using that to track getting payment in a cake app? You can create the paypal form/button in your paypal account, set the url callback so paypal can notify you. Create a table in DB if you want to record the info paypal sends you. Have a method in the controller to handle the POST data from paypal. Here's my code example:
function blah() {
$this->autoRender = false;
// post back to PayPal system to validate
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if (!$fp) {// HTTP ERROR, we should record the data still..?
} else {
fputs($fp, $header . $req);
while (!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res, "VERIFIED") == 0) {// verified from paypal, processing...
} else if (strcmp($res, "INVALID") == 0) {
// oh no, someone is hijacking us...
}
}
fclose($fp);
}
}
What fields to have in the table depends on what you want to keep. Look up the IPN API, and you can setup sandbox testing with paypal.

Related

Show data for only a specific user in Laravel

I am building a Laravel website where people can post tasks into a database. Currently, when I post tasks via my form, every account can see the data. How can I show data for only the specific person who posted it?
My blade file
My task controller file
Thank you very much in advance.
If your user has logged in the you can retrieve their id by Auth::user()->id;
In your index() method remove $tasks = Task::all(); and add
$userId = Auth::user()->id;
$tasks = Task::where('user_id',$userId)->get();
Hope this will help

PrestaShop - Login programmatically

I am writing a android and windows native app. The native app stores the login details as reated for mulitple other web apps, and logs them into this when browsing to them from the native app.
one of the buttons in my app open a prestashop site for a authenticated user. How can i set the username and password and log that user in to the site programmitcally, giving the illusion and user experience that he has been seemlessly authenticated and accessed to his shop.
I know this is an old question, but theres another way which i find better for the purpose.
You include the AuthController from the controllers folder, set your post-parameters and execute the postProcess() method. After this, you can check the "$authController->errors" array for errors. If it's empty - the login was successful.
Example:
public function hookDisplayHeader()
{
if ($this->context->cookie->isLogged())
{
return;
} else {
$acceptLogin = false;
if( isset( $_POST["email"] ) && isset( $_POST["passwd"] ) )
{
$acceptLogin = $this->attemptLogin($_POST["email"],$_POST["passwd"]);
}
if( $acceptLogin )
return;
die( $this->display(__FILE__, 'logintemplate.tpl') );
}
}
protected function attemptLogin($email, $password)
{
include _PS_FRONT_CONTROLLER_DIR_ . "AuthController.php";
$auth = new AuthController();
$auth->isAjax = true;
$_POST["email"] = $email;
$_POST["passwd"] = $password;
$_POST["SubmitLogin"] = true;
$auth->postProcess();
if( count($auth->errors) > 0 )
{
$this->context->smarty->assign( "errors", $auth->errors );
return false;
} else {
return true;
}
}
Edit: This no longer works with Prestashop 1.6. As of PS 1.6 $auth->postProcess() either redirects or sends the ajaxs response immediately. There is no way to circumvent this. If you want to do something after login, you have to make two ajax calls.
Basically do the same as the PrestaShop login form does, which is (for v1.5 at least):
Sending a POST request to http(s)://yourshop.com/index.php?controller=authentication with the following parameters:
email: your customer's email address
passwd: your customer's password
back: name of the controller you want to be redirected to after success (ex: my-account)
SubmitLogin: put anything there, it just needs to be true, so that the controller knows it's a login action
If it doesn't work, your version may work differently and you will have to check the network tab of your favourite developer tool, to see what kind of request is sent with which parameters.

CakePHP perform Auth before Constructing Models?

In my CakePHP application I have multi-tenancy which is provided through isolated databases (each tenant has their own, tenant-specific database).
There is also a 'global' database which contains users and tenancy information. The 'tenants' table contains the name of which database the particular tenant occupies. Each user contains a single tenant_id.
Structure:
global_db:
users (contains tenant_id foreign key)
tenants (contains tenant-specific database name, ie: 'isolated_tenant1_db')
isolated_tenant1_db:
orders
jobs
customers
isolated_tenant2_db:
orders
jobs
customers
This system works correctly when the user is logged in via forms / sessions. When they login through /Users/login their tenancy is verified, stored in Session, and database parameters are loaded so their own 'isolated' models can use this dynamic connection.
However, issues arise when the user tries to login via Basic Auth, and directly request the controller function they want to access. For example /Orders/view/1.xml.
In this case, CakePHP attempts to construct the 'Order' Model before the user has been logged in, and therefore before any tenancy information is available - which means it has no idea what database to connect to in order to access orders.
From putting debug() statements around the place I can see that the order in which models / controllers / auth are constructed / executed is as follows (when executing /Orders/view/1.xml):
Model __construct: User
Controller __construct: OrdersController
Model __construct: Permission
Model __construct: Order
function: OrdersController/beforeFilter
AuthComponent __startup
Model __construct: Models related to Order
My problem is that AuthComponent::_startup is executed after Order Model has been constructed. I need to attempt to login the user (and get their database information) before this 'Order' model is constructed.
Questions:
What causes the User model to be constructed before anything else? (I also have the default CakePHP ACL enabled)
Where in the App can I put a call to Auth->login() to attempt login if the request contains BasicAuth headers, that will be executed prior to trying to load tenant-specific models? I assume putting this inside User __construct is a very bad idea.
== UPDATE 01/05/2014 ==
Inserting code samples.
bootstrap.php:
Checks whether the request is being made to api. subdomain:
// Determine whether the request is coming from the api.* subdomain, and if so set the API_REQUEST define to true.
if (preg_match('/^api\./i',$_SERVER['HTTP_HOST']))
{
define('API_REQUEST',true);
// Any links generated (in emails etc), will contain the full base url. If a cron job logged in via the API is generating
// those e-mails, then users will receive links to api.mydomain, instead of just mydomain.
$full_base_url = Router::fullBaseUrl();
$new_full_base_url = preg_replace('/\/\/api\./i', '//', $full_base_url);
Router::fullBaseUrl($new_full_base_url);
CakeLog::write('auth_base_url_debug', 'modified fullbaseurl from ' . $full_base_url . ' to ' . $new_full_base_url);
}
else
{
define('API_REQUEST',false);
}
AppController.php:
public $components = array(
'Security',
'Session',
'Acl',
'Auth' => array(
'className' => 'ExtendedAuth',
'authenticate' => array(
'FormAlias',
),
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
),
'loginRedirect' => array('controller' => 'Consignments', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'Users', 'action' => 'login'),
),
//'Users.RememberMe',
);
function beforeFilter()
{
// Reroute all requests to API subdomain (ie: api.mydomain) to api_ prefixed actions.
// Also, enable Basic Authentication if the user is accessing via api.*
// If login fails, return 401 error instead of 302 redirect to login page.
if(API_REQUEST == true)
{
$this->params['action'] = 'api_'.$this->params['action']; // prefix the actions with api_
$this->Auth->authenticate = array('BasicAlias'); // Switch to using Basic Authentication
if($this->Auth->login() == false) // Attempt Basic Auth Login
{ // Login failed
CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
header("HTTP/1.0 401 Unauthorized"); // Force returning an Unauthorized header (401)
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
}
}
It is important to note that BasicAlias Auth Component is not included in the $components within AppController, but used dynamically if the request is to the api.* subdomain. However, the order in which classes are constructed has no effect whether BasicAlias AuthComponent is included in $components, or used dynamically as shown above.
AppModel:
function __construct($id = false, $table = null, $ds = null)
{
if(($ds == null) && ($this->use_tenant_database == true))
{
// Create a connection to the tenants database and configure model to use this connection.
$Tenant = ClassRegistry::init('Tenant');
$db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
if($db_name == false)
{
header("HTTP/1.0 500 Server Error"); // Force returning a Server Error Header (500)
debug('AppModel::$db_name = false, unable to proceed');
CakeLog::write('tenant_error', 'db_name = false, unable to connect.');
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
// Point model to the tenant database connection:
$this->useDbConfig = $db_name;
}
parent::__construct($id, $table, $ds);
}
And then within any models which use a specific tenant database:
class Order extends AppModel
{
var $use_tenant_database = true;
...
}
Tenant.php:
/**
* Check whether a connection to the current users tenant database has already been created and if so, return its name.
* Otherwise, create the connection and return its name.
*
* #return boolean|Ambigous <mixed, multitype:, NULL, array, boolean>
*/
public function checkAndCreateTenantDatabaseConnectionForCurrentUser()
{
// Check whether we have the tenants database connection information available in the Configure variable:
if(Configure::check('Tenant.db_name') == true)
{ // the db_config is available in configure, use it!
$db_name = Configure::read('Tenant.db_name');
}
else
{ // The tenants db_name has not been set in the configure variable, we need to create a database connection and then
// set the configure variable.
$tenant_id = $this->getCurrentUserTenantId();
if($tenant_id == null)
{ // Unable to resolve the tenant_id, instead, connect to the default database.
debug('TRIED TO CONSTRUCT MODEL WITHOUT KNOWING TENANT DATABASE!!');
exit;
}
$db_name = $this->TenantDatabase->createConnection($tenant_id);
if($db_name == false)
{ // The database connection could not be created.
CakeLog::write('tenant_error', 'unable to find the database name for tenant_id: ' . $tenant_id);
return false;
}
Configure::write('Tenant.db_name', $db_name);
}
return $db_name;
}
So, if the user requests a URL for example:
http://api.mydomain.com/Orders/getAllPendingOrders
Where they have supplied BASIC auth credentials along with the request, then what happens is that classes are constructed / executed in the following order:
Model __construct: User
Controller __construct: OrdersController
Model __construct: Permission
Model __construct: Order
Model __construct: Tenant
Model __construct: TenantDatabase
function: OrdersController/beforeFilter
AuthComponent __startup --> This then performs the login.
Model __construct: other models.
The problem is: Order.php is being constructed the user has been logged in, which means when the code in AppModel.php is executed:
$db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
It is unable to determine the users current tenancy.
I need to find out a workaround for this, either by somehow performing the login BEFORE Order.php is constructed, or hacking it so that if you attempt to construct a model which has $use_tenant_database = true, and the user is not logged in, then BasicAuth is performed at this point to try and login the user.. however this feels wrong to me.
You might want to have a look at Authorization (who’s allowed to access what) portion in Cake's documentation. Specifically look at the isAuthorized function and how it works.
You might need something like this in your Orders controller:
// app/Controller/OrdersController.php
public function isAuthorized($user) {
// All registered users can add posts
if ($this->action === 'add') {
return true;
}
// The owner of an order can edit and delete it
if (in_array($this->action, array('edit', 'delete'))) {
$orderId = (int) $this->request->params['pass'][0];
if ($this->Order->isOwnedBy($orderId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
Implement your logic in before filter Request Life-cycle callback in the app controller.
Controller::beforeFilter() :
This function is executed before every action in the controller. It’s a handy place to check for an active session or inspect user permissions.
http://book.cakephp.org/2.0/en/controllers.html
It turns out these models were being constructed by the 'Search.Prg' plugin, a CakeDC plugin for handling search / filtering of results. The initialize() function within the component was being executed and causing the model to be constructed prior to the user being logged in.
The way in which this was solved was to move the Basic Auth check / login process from AppController beforeFilter to ExtendedAuthComponent (my own custom authenciation component) initialize function.
The end code was this:
ExtendedAuthComponent.php
public function initialize(Controller $controller)
{
parent::initialize($controller); // Call parent initialization first, this sets up request and response variables.
$this->controller = $controller;
// Reroute all requests to API subdomain (ie: api.rms.roving.net.au) to api_ prefixed actions.
// Also, enable Basic Authentication if the user is accessing via api.*
// If login fails, return 401 error instead of 302 redirect to login page.
if(API_REQUEST == true)
{
$controller->params['action'] = 'api_'.$controller->params['action']; // prefix the actions with api_
if($this->loggedIn() == false) // Attempt Basic Auth Login
{ // Login failed
$this->authenticate = array('BasicAlias'); // Switch to using Basic Authentication
if($this->login() == false)
{
CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
header("HTTP/1.0 401 Unauthorized"); // Force returning an Unauthorized header (401)
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
}
}
}
This causes the user to be logged in via Basic Auth before the Search.Prg components initialize() function is run, which means the users tenancy is determined before the model(s) are constructed, solving the problem.

Pypal IPN VERIFIED but Paypal keep retrying

I am testing IPN In sandbox. At my website I log this communication between Paypal and my site
Start validations:_notify-validate,16.94,Eligible,confirmed,SVWAAUVZX2S4Y,0.00,1 Main St,13:57:50 May 07, 2012 PDT,Completed,windows-1252,95131,Test,0.84,US,Test User,3.4,,verified,demarc_1336421374_biz#gmail.com,United States,San Jose,1,As4zhnwQeMSnsOdh0NBxc2GkfRrEAd8OPZnnd3EWRDz38L81PzX-6vI- ,demarc_1321350258_per#gmail.com,6FT25620DT6366410,instant,User,CA,demarc_1336421374_biz#gmail .com,,6SHJZ2PT5YMLS,web_accept,test item,EUR,,US,1,0.00,test item,,0.00,de0561482901
2012-05-07 22:52:47 Paypal: ResponseVERIFIED
2012-05-07 22:52:47 Paypal: 1
2012-05-07 22:52:47 Paypal: Processing Trasaction: 4fa8361f-7574-444f-b821-5e660a00000f
2012-05-07 22:52:47 Paypal: 4fa8361f-7574-444f-b821-5e660a00000f
It seems ok, uh? But if I login into Sandbox with the business account, in IPN History I see that Paypal is keeping trying to send IPN. At my website, where I log transaction into database, I see a lot of records (which I store if the response is verified).
How is it possible?
I do a post back to Paypal with cakephp
function isValid($data ){
$data['cmd'] = '_notify-validate';
$newData['cmd'] = '_notify-validate';
foreach ($data AS $key => $val) {
$newData[$key] = $val;
}
$this->log("Start validations:".join(",",$newData), 'paypal');
$data = array_map(array('PaypalIpnSource', 'clearSlash'), $newData);
if (isset($data['test_ipn'])) {
$server = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
} else {
$server = 'https://www.paypal.com/cgi-bin/webscr';
}
$response = $this->Http->post($server, $newData);
$this->log("Response".$response, 'paypal');
if ($response == "VERIFIED") {
return true;
}
if (!$response) {
$this->log('HTTP Error in PaypalIpnSource::isValid while posting back to PayPal', 'paypal');
}
return false;
}
}
I do not think it is a problem of cakephp or whatever.
I set in the sandbox business account EUR as currency. And also the currency value in the paypal submission form is in EUR.
Any help appreciated after days of googling.
Your IPN endpoint must reply with HTTP status code 200 to the PayPal's notification. Otherwise the status will stay "Retrying".

CakePHP - Create an Offline/Online feature for a site

======================================== EDIT ====================================
Per charles suggestion, I accomplished the Offline/Online feature using the following code, based on Charles code:
<?php
Class AppController extends Controller{
// prevents unauthorized access
public $components = array('Auth');
// the name of the model storing site_offline boolean
public $uses = array('Configuration');
// callback invoked before every controller action
public function beforeFilter() {
// returns your site_offline status assuming 0 is offline
if ($this->Configuration->get_site_status() == 1) {
$this->Auth->allow('*');
}else {
if(($this->Configuration->get_site_status() == 0) and (!$this->Auth->user() == null)){
// I set it up like this for now to allow access to any authenticated user,
//but later will change it to only allow admins access thru a login form
$this->Auth->allow('*');
}else{
//If site is offline and user is not authenticated, sent them to
// the a screen using the OFFLINE layout and provide a screen for login.
$this->layout = 'offline';
$this->setFlash('Maintenance Mode. Check back shortly.');
$this->Auth->deny('*');
}
}
}
}
?>
Then I used jQuery to hide my login form. An admin clicks on the message to show the login form. This is an attempt to prevent any login tryouts.
============================ END EDIT ==========================================
I would like to know what is the best way to create a "site offline/online" feature in CakePHP. Basically, I would like to allow an administrator to turn off access to the site to everyone registered or not. The offline page should have a login access thru which only admins can login.
The idea I have is to create some kind of dashboard controller, where as soon as the administrator is logged in he/she will be redirected to this dashboard from where he can access the other controller actions (admin_edit, etc). This dashboard and all admin actions (admin_delete, etc) should use the admin layout.
Is this a good approach? For the offline/online feature should I create a settings table with a site_offline field that can be turned on or off? Where in app_controller and what code should I use to check for it before allowing or not access to the site?
Thanks a lot for your help,
first add a config in the core.config
/*
* This is the site maintenance
* The built in defaults are:
*
* - '1' - Site works
* - '0' - site down for maintenance.
*/
Configure::write('Site.status', 1);
in the AppController you'll check it in the beforeRender function
if (Configure::read('Site.status') == 0) {
$this->layout = 'maintenance';
$this->set('title_for_layout', __('Site_down_for_maintenance_title'));
} else {
// do something
}
i'm here load a separate layout form the maintenance to let me add whatever layout i want
If you were going to save site_offline boolean value in a database table you should be able to easily do this with a callback in AppController and the Auth component.
<?php
AppController extends Object {
// prevents unauthorized access
public $components = array('Auth');
// the name of the model storing site_offline boolean
public $uses = array('NameOfModel');
// callback invoked before every controller action
public function beforeFilter() {
// returns your site_offline status assuming 0 is offline
if ($this->NameOfModel->get_status() === 0) {
$this->Auth->deny('*');
} else {
$this->Auth->allow('*');
}
}
}
I've always liked the idea of the DashboardsController for admin functions. That's actually the exact name of the class I use and the same general idea.

Resources