Friday, 7 October 2011

Magento External Login and Buy

I needed a way for a user to login to a Magento shop from an external website and have a selected product automatically added to their cart.

First off you'll need to create an API user, there are lots of tutorials out there on how to do this. Make sure this user has access to at least "Customers->Retrieve customer info" and "Category Inventory->Retrieve stock data".

You'll obviously need a form on your external site for the customer to enter their details and select which product they want to buy. The following is just an (untested) example form to give you a rough idea.
Please note: You'll notice I've included an iframe pointing to the shops login page, this is important as it will create the required cookies for the customer to login. If you don't include this iframe any customer who is missing the required cookies won't be logged in and will have nothing added to their cart!

<iframe src="https://shop.address/customer/account/login/" style="position:absolute;height:1px;width:1px;top:-100px;left:-100px;"></iframe>
<form action="your_external_script.php" method="post">
	<div class="login_field">
		<label>Choose your product</label>
		<div class="product_option">
			<label for="product_1">Product #1</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="1" />
		</div>
		<div class="product_option">
			<label for="product_2">Product #2</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="2" />
		</div>
		<div class="product_option">
			<label for="product_3">Product #3</label>
			<!-- Value below should be a real product
			in Magento that the user has access to! -->
			<input type="radio" name="login[product_id]" value="3" />
		</div>
	</div>
	<div class="login_field">
		<label for="login_username">Username</label>
		<input id="login_username" type="text" value="" name="login[username]" />
	</div>
	<div class="login_field">
		<label for="login_password">Password</label>
		<input id="login_password" type="password" value="" name="login[password]" />
	</div>
	<div class="login_field">
		<input type="submit" value="Buy selected product" />
	</div>
</form>

Now when the user submits that data you'll have to check three things, that the user exists, the password is correct and that the product is in stock and available.
<?php
/**
 * Validates a Magento password
 *
 * @param	string		$password: The user entered password
 * @param	string		$hash: The password hash from Magento customer info
 * @return	boolean		Returns true if the password is valid
 */
function validatePassword($password, $hash) {
	$hashArr = explode(':', $hash);
	switch(count($hashArr)) {
		case 1:
			return md5($password) === $hash;
		case 2:
			return md5($hashArr[1].$password) === $hashArr[0];
	}
	return false;
}
$apiUrl = 'https://shop.address/api/?wsdl';
$apiUser = 'my_api_user';
$apiKey = 'my_api_key';

if(isset($_POST['login'])) {
	try {
		$client = new SoapClient($apiUrl);
		$session = $client->login($apiUser, $apiKey);
	} catch (Exception $e) {
		echo '<p class="error">Network error, please try again later.</p>';
		exit;
	}
	//Check selected product is in stock...
	try {
		$product = $client->call($session, 'product_stock.list', array(intval($_POST['login']['product_id'])));
	} catch (Exception $e) {
		echo '<p class="error">'.$e->getMessage().'</p>';
		exit;
	}
	if(empty($product)) {
		echo '<p class="error">Product doesn\'t exist.</p>';
		exit;
	} else if($product[0]['is_in_stock'] == 0 || $product[0]['qty'] == 0) {
		//You could possible add the title of
		//the product here, I am lazy though and didn't look up where to get it from
		echo '<p class="error">The product you chose has sold out.</p>';
		exit;
	}
	//Lookup customer record
	list($customer) = $client->call(
		$session,
		'customer.list',
		array(
			array(
				'email' => addslashes($_POST['login']['username'])
			)
		)
	);
	if(is_array($customer)) {
		if(validatePassword($_POST['login']['password'], $customer['password_hash'])) {
			echo '<form action="https://shop.address/login_buy.php" method="post">';
			echo '<input type="hidden" name="login[username]" value="'.$customer['email'].'" />';
			echo '<input type="hidden" name="login[password]" value="'.$customer['password'].'" />';
			echo '<input type="hidden" name="login[product_id]" value="'.intval($_POST['login']['product_id']).'" />';
			echo '<input type="submit" value="Proceed to checkout" />';
			echo '</form>';
			exit;
		} else {
			echo '<p class="error">Invalid login or password</p>';
			exit;
		}
	} else {
		echo '<p class="error">Invalid login or password</p>';
		exit;
	}
}

?>
This then posts data to a custom script you'll have to add to the index of your Magento shop:
<?php
// Load the Magento core
require_once 'app/Mage.php';
umask(0);
Mage::app()->setCurrentStore('default');

Mage::getSingleton("core/session", array("name" => "frontend"));
$session = Mage::getSingleton("customer/session");

//Log out any existing sessions
if(!$session->isLoggedIn()) {
	$session->logout();
}
//Log user in
$login = Mage::getSingleton('core/app')->getRequest()->getPost('login');
$session->login($login['username'], $login['password']);

$productId = $login['product_id'];

header('Location: https://shop.address/checkout/onepage/add?product='.$productId.'&qty=1');
exit;
?>

1 comment:

  1. You are a life saver!!

    I had searched high and low for a simple method of validating the user password through the API and there was no mention of it anywhere, even in Magento's documentation.

    This post did all that in one go, complete with error checking, and made my day :)

    Many thanks,

    Darren

    ReplyDelete