ClickMagick Support is Top Notch

ClickMagick is a click tracking service used by thousands of internet marketers because of it’s features and ease of use.

My website track member’s transaction on clickbank, jvzoo and other networks, then update their clickmagick statistics through postback URL.

The problem occurs when my website tracked a refund/chargeback,  i need to post this negative amount back to member’s clickmagic statistics. Clickmagick didn’t allow this, post data with negative value was simply be ignored.

I wrote a suggestion through their form and i received a response from Patrick Kelly, a very friendly dude, knowledgeable, know exactly what i want since the very first email. Definitely not a virtual assistance.

My very uncommon request was heard and executed in four day straight.

While some other companies might not even response to my request, or simply responding “Sorry, this is how we do it. Don’t like it? GTFO!”

Here’s how the Global Postback URL works:

Say someone ordered a product from you, this is the postback url used to update your clickmagick stats:

http://www.clkmg.com/api/s/post/?uid=[USERID]&amt=1.23&s1=[CLICKID]&s2=&s3=&s4=&s5=&ref=receiptid

You will need your clickmagic userid (not username), transaction value, clickmagick’s clickid and receipt number.

That code there will record the transaction you can see on your link statistics.

Now the user is not satisfied and asked for refund, you processed the refund, and got notified by clickbank or any payment processor you use.

You need to use this code to update your clickmagick statistics:

http://www.clkmg.com/api/s/post/?uid=[USERID]&amt=-1.23&s1=[CLICKID]&s2=&s3=&s4=&s5=&ref=receiptid

Everything is the same as the first link, with exception of amt variable value. It’s -1.23 now instead of 1.23

However it simply didn’t work. Transaction is not recorded on clickmagick, thus making your stat inaccurate and could made you purchase some more traffic from your source without a good result.

Now that Patrick has some update made to the system, your stats will be accurate again. Transaction with negative value is recorded and processed on clickmagick. It will delete the corresponding transaction.

Note: It must have same ref value, and same absolute value for amt variable.

At the end of our conversation Patrick said “Thanks for your help”, and i replied “My help? What help? It’s you who helped me.” (for making this feature available)

This is the last email i received:

“Dedy,

You helped us make ClickMagick even better, so thanks! 🙂

Patrick @ ClickMagick”

Wow, it is nice to be heard and appreciated.

Thanks clickmagick, Patrick, for listening to your customers and act super fast 🙂

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

How a Thank You Page Could Decrease Refund Rate and Increase Sales

Do you know that a thank you page could decrease refund rate and increase sales?

Thank you page shouldn’t be looking so plain and boring. Someone just give you their money and you give them nothing but “Your credit card or bank statement will show a charge by …” and “Click Here To Continue” button? I’ll shake their hand and give them a big hug when someone give me money on real life.

Clickbank Thank You Page Guide

Affiliate networks like clickbank, jvzoo and some others pass variables to your thank you page. This is mainly used to verify if transaction is valid.

However You can use those variables for other purpose. Those variables usually consist of these:
– transaction id
– product id
– name
– email
– price
– affiliate id

When sending email to your list, to be friendly to our subscribers, We use “Hi FirstName!” instead of just “Hi!” right? There’s no reason we can’t use it on a thank you page so the customer feels welcomed. Use “Thank You FirstName!” instead of simple “Thank You!”

Tell the customer what product he just bought, what the benefits are, how to get the product and how to proceed.

If it is your front end product then you can shortly explain your up sell offer so they could be prepared and keep their wallet on their hand instead of putting it back to the drawer.

I put and image of a platinum badge on my thank you page when customer purchased a platinum membership.

You can get it even merrier by placing animated image of exploding birthday balloon to congratulate them. For what? For making the right decision and purchase your product.

If you are a single person, use singular instead of plural terms when describing yourself. It will make the whole sales process more personal.

Try it and let me know how it goes for you.

VN:F [1.9.20_1166]
Rating: 10.0/10 (1 vote cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

CakePHP3: User Authentication

Create users controller to manage, you guess it! users. Login, logout, signup, forgot password, change password, change email, update profile etc

add this line

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Auth\DefaultPasswordHasher;

and add this method to the class

protected function _setPassword($value)
{
    $hasher = new DefaultPasswordHasher();
    return $hasher->hash($value);
}

Add some groups:

http://example.com/groups/add
  1. Admin
  2. Manager
  3. User

Now add some users, one for each groups

http://example.com/users/add

load the auth component, checking username and password field.

put it on initialize method

$this->loadComponent('Auth', [
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						]
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

All page now can’t be accessed unless you are logged in. Add this below the code above so we can still visit homepage and other /pages/*

$this->Auth->allow(['display']);

I want users to be logged in for 30 days unless they logout

Update /config/app.php from

'Session' => [
        'defaults' => 'php',
    ],

to

'Session' => [
        'defaults' => 'php',
		'timeout' => 60*60*24*30,
		'ini' => [
				'session.cookie_lifetime' => 60*60*24*30
		]
    ],

add this login method to UsersController.php

It will redirect user to default login redirect url if logged in user is detected. Will also update last_login field

public function login()
	{
		//user already logged in? take to default login redirect
		if($this->Auth->user('id'))
			return $this->redirect($this->Auth->redirectUrl());
		
		if ($this->request->is('post')) {
			$user = $this->Auth->identify();

			if ($user) {
				$this->Auth->setUser($user);
				
				//update last login
				$user = $this->Users->get($user['id']);
				$user->last_login = date("Y-m-d H:i:s");
				$this->Users->save($user);
				
				return $this->redirect($this->Auth->redirectUrl());
			}
			$this->Flash->error('Your username or password is incorrect. Or account expired.');
		}
	}

and create the login form

<h1>Access Your Account</h1>
<?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Access Your Account') ?></legend>
        <?php
            echo $this->Form->input('email');
            echo $this->Form->input('password');
        ?>
    </fieldset>
    <?= $this->Form->button(__('Login')) ?>
    <?= $this->Form->end() ?>
    
    <div class="columns">
	<?php echo $this->Html->link(__("Forgot You Password?"),['action'=>'forgotPassword']);?>
	</div>

On UsersController.php add initialize method. $this->Auth->allow() used to tell cakephp which method are accessible to public, everyone can access them without having to login.

Add some public method you will add later that you can think of right now to save time.

public function initialize()
{
	parent::initialize();
	$this->Auth->allow(['logout','signup','forgotPassword','resetPassword','profile']);
}

create logout method, no need to create a view file for this

public function logout()
{
	$this->Flash->success('See You Soon!');
	return $this->redirect($this->Auth->logout());
}

deny all access, except those set by controller $this->Auth->allow()

public function isAuthorized($user)
{
    return false;
}

add this line

$this->loadComponent('Auth', [
        		'authorize' => 'Controller',
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						]
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

You should add this to each of controller you have

Now give user some access

index, changePassword, ChangeEmail, updateProfile method. We don’t need some condition for those, give them access (line 5-7)

Some other methods, something like /users/edit/1 or /users/view/1 we need to check and compare it current user and decide if user has access to it (line 12-16)

public function isAuthorized($user)
	{
		$action = $this->request->params['action'];
	
		if (in_array($action, ['index', 'changePassword', 'changeEmail', 'updateProfile'])) {
			return true;
		}

		if (empty($this->request->params['pass'][0])) {
			return false;
		}
	
		$id = $this->request->params['pass'][0];
		$usr = $this->Users->get($id);
		if ($usr->id == $user['id']) {
			return true;
		}
		return parent::isAuthorized($user);
	}

I also want users who login meets this condition:

expire field > now and active = ‘yes’

the code below tell auth component to use findAuth method to check if user is authorized

$this->loadComponent('Auth', [
        		'authorize' => 'Controller',
        		'authenticate' => [
        				'Form' => [
        						'fields' => [
        								'username' => 'email',
        								'password' => 'password'
        						],
        						'finder' => 'auth'
        				]
        		],
        		'loginAction' => [
        				'controller' => 'Users',
        				'action' => 'login'
        		],
        		'unauthorizedRedirect' => $this->referer() // If unauthorized, return them to page they were just on
        ]);

here’s the findAuth method

public function findAuth(\Cake\ORM\Query $query, array $options)
    {
    	$query
    	//->select(['id', 'username', 'password'])
    	->where(
    			[
    					'Users.expire >=' => date("Y-m-d H:i:s"),
    					'Users.active' => 'yes'
    			]
    		);
    
    	return $query;
    }

By the way, I’m going to use the exception

namespace App\Controller;

use App\Controller\AppController;
use Cake\Core\Exception\Exception;
public function validationChangePassword(Validator $validator) {
    	$validator
    		->add('old_password','custom',[
    			'rule' => function($value, $context) {
    				$user = $this->get($context['data']['id']);
    				if($user) {
    					if((new DefaultPasswordHasher)-&gt;check($value,$user-&gt;password)) <a style="color:#000;text-decoration:none" href="http://cococheats.com/episode-choose-your-story-hack/">episode passes</a>  {
    						return true;
    					}
    				}
    			},
    			'message' =&gt; 'The old password does not match the current password!'
    		])
    		-&gt;notEmpty('old_password');
    
    	$validator
    		-&gt;add('new_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('new_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','confirm_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('new_password');
    		
    	$validator
    		-&gt;add('confirm_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('confirm_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','new_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('confirm_password');

    	return $validator;
    }
    
	public function validationResetPassword(Validator $validator) {
    	$validator
    		-&gt;add('new_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('new_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','confirm_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('new_password');
    		
    	$validator
    		-&gt;add('confirm_password',[
    			'length' =&gt; [
    				'rule' =&gt; ['minLength', 8],
    				'message' =&gt; 'The password have to be at least 8 characters!'
    			]
    		])
    		-&gt;add('confirm_password',[
    			'match' =&gt; [
    				'rule' =&gt; ['compareWith','new_password'],
    				'message' =&gt; 'The passwords does not match!'
    			]
    		])
    		-&gt;notEmpty('confirm_password');

    	return $validator;
    }
    
    public function validationSignup(Validator $validator)
    {
    	$validator
    	-&gt;integer('id')
    	-&gt;allowEmpty('id', 'create');
    
    	$validator
    	-&gt;requirePresence('username', 'create')
    	-&gt;notEmpty('username')
    	-&gt;add('username', 'unique', ['rule' =&gt; 'validateUnique', 'provider' =&gt; 'table']);
    
    	$validator
    	-&gt;requirePresence('password', 'create')
    	-&gt;notEmpty('password');
    
    	$validator
    	-&gt;email('email')
    	-&gt;requirePresence('email', 'create')
    	-&gt;notEmpty('email');
    
    	$validator
    	-&gt;requirePresence('first_name', 'create')
    	-&gt;notEmpty('first_name');
    
    	$validator
    	-&gt;requirePresence('last_name', 'create')
    	-&gt;notEmpty('last_name');
    
    	return $validator;
    }

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

My Programming Tools

OS: Windows 10 64bit
PHP IDE: Zend Studio 13.5
AMP: Xampp
Main Browser: Firefox
SSH Client: Bitvise
FTP Client: CuteFTP

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

CakePHP 3: It’s time!

I’ve been stuck with cakephp 1.3 for some time now, I guess it’s time to get this deliciously looking red velvet.

OS: Windows 10 64bit
AMP: Xampp zip download put on D:\xampp
Editor: Zend Studio 13.5
Composer installed globally

cd d:\xampp\htdocs
composer self-update && composer create-project --prefer-dist cakephp/app example.com

Here’s my basic users and groups table

database schema for users and groups

CREATE TABLE IF NOT EXISTS `groups` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  `group_id` int(11) NOT NULL,
  `username` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `first_name` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_name` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `address` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `address2` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `city` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `zip_code` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `country` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `phone_number` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `ip` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `active` enum('no','yes') COLLATE utf8_unicode_ci DEFAULT 'no',
  `last_login` datetime DEFAULT NULL,
  `token` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `token_expire` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

ALTER TABLE `users`
  ADD CONSTRAINT `group_key` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            //'port' => 'non_standard_port_number',
            'username' => 'username',
            'password' => 'password',
            'database' => 'database',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,
            'quoteIdentifiers' => false,
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
            'url' => env('DATABASE_URL', null),
        ],
cd d:\xampp\htdocs\example.com

do this for each database table, also when you add new table to database

bin\cake bake all tablename

create a virtual host for example.com

<VirtualHost *:80>
    ServerAdmin postmaster@example.com
    DocumentRoot "/xampp/htdocs/example.com"
    ServerName example.com
    ServerAlias example.com
    ErrorLog "logs/example.com-error.log"
    CustomLog "logs/example.com-access.log" combined

    <Directory "/xampp/htdocs/example.com">
       Options Indexes FollowSymLinks
       AllowOverride All
       Order allow,deny
       Allow from all
    </Directory>
</VirtualHost>

Open notepad as administrator, open C:/Windows/System32/drivers/etc/hosts and add this line

127.0.0.1 example.com

restart apache from xampp control panel

Open firefox and go to this url

http://example.com

and you can see your cakephp installation

VN:F [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS