Tiden går så fort när man har roligt! Förutom några lediga veckor här och där har både jag och Raphael jobbat flitigt i sommar med Progmera:s kundprojekt och även lite egna projekt. Vi kan ta några exempel på sommarens aktiviteter;

Aqua2musicAqua2music
Här kom vi in i ett projekt där de haft konsulter tidigare som arbetat med systemet men att de nu ville ta hem arbetet och hitta någon utvecklare i närheten så de kunde få bättre koll. Aqua2music drivs av Maria Ström och är ett företag som riktat in sig mot vattengympa med bl.a. mycket filmer och instruktioner för övningar och hela program. De ville att vi skulle koppla på en betalmodul till att börja med, men det visade sig vara mer än så. Tjänsten var väldigt trasig och många saker fugerade inte alls. Vi la flera dagar på att bara stabilisera så att man kunde använda den funktionalitet som de börjat lägga in. Där efter byggde vi på systemet och förbättrade så att Maria kunde lansera tjänsten ut mot kunderna för några veckor sedan. Ni kan följa företaget på facebook här.

Nya hemsidor
Vi har även fått in en handfull förfrågningar på nya hemsidor och dessa bygger vi med hjälp av CMS-verktyget WordPress. Här hjälper vi till med design och funktionalitet. Vi hjälper även till att lägga in texter och bilder som kunderna tillhandahåller. Detta erbjuder vi till fast pris eller en abonnemangskostnad där vi hjälper till löpande. Och just nu håller vi på med sidor för ett par guideföreningar och ska ta fram ny sida för Svenska guideförbundet.

Egna projekten
Här finns det mycket att säga, så det kommer presenteras närmare i några egna blogginlägg framöver. Men jag kan lite snabbt nämna att både Fakturaarkivet och Gymsystem är två produkter som är väl fungerande och används av flera kunder. Vill ni inte vänta tills jag skriver lite mer om dem kan ni alltid ta ett besök på deras egna hemsidor.

Fakturaarkivet

Det var länge sedan jag satt på en Windows XP dator, men tyvärr sitter flera av Progmera:s kunder kvar med det. Detta trots att vi hunnit förbi både Windows Vista, Windows 7 och nu är inne på Windows 8. Så länge som Microsoft har kommit med uppdateringar så har det inte varit några problem, men sedan 8 april i år så upphör nu supporten för XP (Läs mer här). Alltså hög tid för att uppgradera, och min rekommendation är att uppgradera till senaste Windows 8. Tyvärr är det många som inte alls gillar det nya Windows, men där håller jag inte med. Windows 8 är betydligt snabbare och för en administratör är det även en hel del saker som förbättrats. Det är en lite annorlunda meny, men då man alltid kan trycka på Windows-knappen och sen skriva vad man vill starta för program så tänker man inte så mycket på det. Men som alltid är det en vanesak och har man vant sig med Windows XP under tolv år så förstår jag att det kan vara svårt att byta, men tyvärr är det ett måste.

En kund fick tyvärr problem här i veckan då hans gamla dator slutade fungera helt. Det gick inte göra så mycket mer än att ta ut hårddisken och föra över filerna till ny dator med nytt operativsystem. Då kunden hade fler datorer med Windows XP på så har jag under dagen fortsatt att hjälpa denna kund med att installera en annan dator med nytt Windows, Office, löneprogram mm. Jag passade även på att skruva i en SSD disk så nu hoppas jag de märker skillnad på hastigheten i arbetet när de kommer tillbaka från semestern.

 

Jag har tidigare implementerat kortbetalning för Payson och betalning genom Klarna i olika webbutiker. I fall då kunden använt en webbshop i WordPress, till exempel WooCommerce eller Jigoshop, så har jag oftast använt färdiga plugin som jag sedan anpassat för kunden. Nu fick jag frågan från Mondido, som är en ny betalningsförmedlare på markanden, om jag inte kunde bygga ett plugin som använder deras Hosted Window. Det är ganska trevligt att bygga ett plugin för WooCommerce då man gör ett vanligt WordPress plugin och installerar på vanligt sätt.

Nedan ser ni koden för pluginet, vill ni använda pluginet kan ni ladda ner det här.

<?php

/*
  Plugin Name: Mondido (Hosted Windows)
  Plugin URI: https://mondido.com/
  Description: Mondido Payment gateway for woocommerce
  Version: 1.1
  Author: Mikael Andersson
  Author URI: http://progmera.se/
 */

// Actions 
add_action('plugins_loaded', 'woocommerce_mondido_init', 0);
add_action('init', array('WC_Gateway_Mondido', 'check_mondido_response',));
add_action('valid-mondido-callcack', array('WC_Gateway_Mondido', 'successful_request',));

function woocommerce_mondido_init() {
	if (!class_exists('WC_Payment_Gateway')) {
		return;
	}

	class WC_Gateway_Mondido extends WC_Payment_Gateway {

		public function __construct() {

			$this->id = 'mondido';
			$this->medthod_title = 'Mondido';
			$this->medthod_description = __('', 'mondido');
			$this->icon = WP_PLUGIN_URL . "/" . plugin_basename(dirname(__FILE__)) . '/mondido.png';
			$this->has_fields = false;
			$this->order_button_text = __('Proceed to Mondido', 'mondido');
			$this->liveurl = 'https://pay.mondido.com/v1/form';

			// Load forms and settings
			$this->init_form_fields();
			$this->init_settings();

			// Get from users settings
			$this->title = __('Mondido', 'mondido');
			$this->description = __('Pay securely by Credit or Debit card or internet banking through Mondido.', 'mondido');
			$this->merchant_id = $this->settings['merchant_id'];
			$this->secret = $this->settings['secret'];
			$this->currency = get_woocommerce_currency();
			$this->test = $this->settings['test'];

			// Actions
			add_action('woocommerce_api_' . strtolower(get_class()), array($this, 'check_mondido_response'));
			if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>=')) {
				add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
			} else {
				add_action('woocommerce_update_options_payment_gateways', array($this, 'process_admin_options'));
			}
			add_action('woocommerce_receipt_mondido', array($this, 'receipt_page'));
		}

		/*
		 * Function for get settings variabler in hash functions.
		 */
		public function get_secret() {
			return $this->secret;
		}

		public function get_merchant_id() {
			return $this->merchant_id;
		}

		public function get_currency() {
			return $this->currency;
		}

		/*
		 * Initialise settings form fields 
		 */
		function init_form_fields() {
			$this->form_fields = array(
				'enabled' => array(
					'title' => __('Enable/Disable', 'mondido'),
					'type' => 'checkbox',
					'label' => __('Enable mondido Payment Module.', 'mondido'),
					'default' => 'no'),
				'merchant_id' => array(
					'title' => __('Merchant ID', 'mondido'),
					'type' => 'text',
					'description' => __('Merchant ID for Mondido')),
				'secret' => array(
					'title' => __('Secret', 'mondido'),
					'type' => 'text',
					'description' => __('Given secret code from Mondido', 'mondido'),
				),
				'test' => array(
					'title' => __('Test', 'mondido'),
					'type' => 'checkbox',
					'label' => __('Set in testmode.', 'mondido'),
					'default' => 'no')
			);
		}

		/*
		 * Create Admin page for settings
		 */
		public function admin_options() {
			echo '<h3>' . __('Mondido', 'mondido') . '</h3>';
			echo '<p>' . __('Mondido, Simple payments, smart functions', 'mondido') . '</p>';
			echo '<table class="form-table">';
			// Generate the HTML For the settings form.
			$this->generate_settings_html();
			echo '</table>';
		}

		/*
		 *  There are no payment fields for mondido, but we want to show the description if set.
		 */
		function payment_fields() {
			if ($this->description) {
				echo wpautop(wptexturize($this->description));
			}
		}

		/*
		 * Generate Mondido button link
		 */
		public function generate_mondido_form($order_id) {

			global $woocommerce;
			$order = new WC_Order($order_id);

			$cart = new WC_Cart();
			$cart->get_cart_from_session();
			$metadata = json_encode($cart);
			$amount = number_format($order->order_total, 2, '.', '');
			$merchant_id = trim($this->merchant_id);
			$currency = trim($this->currency);
			$customer_id = '';
			$hash = generate_mondido_hash($order_id);
			$mondido_args = array();
			$mondido_args = array(
				'amount' => $amount,
				'merchant_id' => $merchant_id,
				'currency' => $currency,
				'customer_ref' => $customer_id,
				'payment_ref' => $order_id,
				'hash' => $hash,
				'success_url' => $this->get_return_url($order),
				'error_url' => $order->get_cancel_order_url(),
				'metadata' => $metadata,
				'test' => $this->test
			);

			$mondido_args_array = array();
			foreach ($mondido_args as $key => $value) {
				$mondido_args_array[] = "<input type='hidden' name='$key' value='$value'/>";
			}
			return '<form action="' . $this->liveurl . '" method="post" id="mondido_payment_form">
            ' . implode('', $mondido_args_array) . '
				<div class="payment_buttons">
					<input type="submit" class="button alt" id="submit_mondido_payment_form" value="' . __('Pay via Mondido', 'mondido') . '" /> <a class="button cancel" href="' . $order->get_cancel_order_url() . '">' . __('Cancel order &amp; restore cart', 'mondido') . '</a>
				</div>
            </form>';
		}

		/*
		 * Process the payment and return the result
		 */
		public function process_payment($order_id) {
			global $woocommerce;
			$order = new WC_Order($order_id);
			return array(
				'result' => 'success',
				'redirect' => $order->get_checkout_payment_url(true)
			);
		}

		/*
		 * Receipt Page
		 */
		public function receipt_page($order) {
			echo '<p>' . __('Thank you for your order, please click the button below to pay with Mondido.', 'mondido') . '</p>';
			echo $this->generate_mondido_form($order);
		}

		/*
		 * Check for valid mondido server callback
		 */
		public function check_mondido_response() {
			$_GET = stripslashes_deep($_GET);
			do_action("valid-mondido-callcack", $_GET);
		}

		/*
		 * Successful Payment
		 */
		public function successful_request($posted) {
			global $woocommerce;
			// If payment was success
			if ($posted['status'] == 'approved') {
				$order = new WC_Order((int) $posted["payment_ref"]);
				$order->update_status('on-hold', __('Awaiting cheque payment', 'woocommerce'));

				// Check so payment is correct
				$hash = generate_mondido_hash((int) $posted["payment_ref"], true, $posted["status"]);
				if ($hash == $posted['hash']) {
					$order->add_order_note(__('Callback completed', 'mondido'));
					$order->add_order_note('transaction_id: ' . $posted['transaction_id']);
					$order->payment_complete();
					$woocommerce->cart->empty_cart();
				} else {
					$order->add_order_note(__('Callback completed', 'mondido'));
					$order->add_order_note('transaction_id: ' . $posted['transaction_id']);
					$order->add_order_note(__('Hash not correct, fake payment?'));
				}
			}
		}
	}

	/*
	 * Generate momondo hash
	 */
	function generate_mondido_hash($order_id, $callback = false, $status = "") {
		$order = new WC_Order((int) $order_id);
		$mondido = new WC_Gateway_Mondido();
		$amount = number_format($order->order_total, 2, '.', '');
		$merchant_id = trim($mondido->get_merchant_id());
		$secret = trim($mondido->get_secret());
		$customer_id = '';
		$currency = strtolower($mondido->get_currency());
		if ($callback) {
			$str = "" . $merchant_id . "" . $order_id . "" . $customer_id . "" . $amount . "" . $currency . "" . strtolower($status) . "" . $secret . "";
		} else {
			$str = "" . $merchant_id . "" . $order_id . "" . $customer_id . "" . $amount . "" . $secret . "";
		}
		return MD5($str);
	}

	add_filter('generate_mondido_hash', 'generate_mondido_hash');

	/*
	 * Add the Gateway to WooCommerce
	 */
	function woocommerce_add_mondido_gateway($methods) {
		$methods[] = 'WC_Gateway_Mondido';
		return $methods;
	}

	add_filter('woocommerce_payment_gateways', 'woocommerce_add_mondido_gateway');

	function init_mondido_gateway() {
		$plugin_dir = basename(dirname(__FILE__));
		load_plugin_textdomain('mondido', false, $plugin_dir . '/languages/');
	}

	add_action('plugins_loaded', 'init_mondido_gateway');

	function WC_Gateway_Mondido() {
		return new WC_Gateway_Mondido();
	}

	if (is_admin()) {
		add_action('load-post.php', 'WC_Gateway_Mondido');
	}
}

?>

Jag har tidigare jobbat mot många olika API:er, men för första gången har jag nu gjort en funktion som arbetar mot Starweb API. Uppdraget gick ut på att läsa av en excelfil och sen jämföra pris och leveransstatus i filen mot alla produkter som hämtades från Starweb. Om en ändring hade gjorts så skulle produkten uppdateras, så inga konstigheter med det. Det var heller inga problem att hämta alla produkter och gå igenom dem för att jämföra förändringar. Men när det kom till att uppdatera produkten började det tråkiga. Då det bara var pris som skulle uppdateras tänkte jag att det räcker med att skicka diverse ID samt priset i XML:en. Produkten uppdaterades, men tyvärr raderades alla parametrar för produkten. Efter kontakt med supporten på Starweb så visade det sig att man i princip behövde skicka med hela produktspecifikationen i XML:en, så som den såg ut när man hämtade den, men inte riktigt. Vissa saker, som kategori, skulle inte med och om det fanns tomma värden skulle inte de heller skickas med. Så istället för att lagra undan bara id på produkt som skulle uppdateras så fick hela produkten lagras och sen gå igenom en ”ränsa-funktion” innan den senare kunde skickas. Lite omständligt för att bara uppdatera prisinformationen tycker jag, något jag även framfört till dem på Starweb, och enligt dem så jobbar de på en ny version av API:et som ska vara klart i höst. Men för uppdraget gjordes ändå en lösning för denna version av API:et så att de nu smidigt kan köra uppdateringen av produkterna.

Lite exempel på hur kod kan se ut:

// CODE for include and start..

// Get all product and save it temporary
$log->reportInfo('Hämta alla produkter');
list($sResponse, $iHttpResponseCode) = restRequest('GET', '/products');
$starwebXml = new SimpleXMLElement($sResponse);
// Check If the XML status code indicate an error
if ($starwebXml->apiInfo->statusCode != 1) {
	$log->reportWarning(null, 'Hämtning misslyckad (' . $starwebXml->apiInfo->statusCode . ') ' . $starwebXml->apiInfo->statusMessage);
} else {
	// Save all producs from starweb
	$allProduct = array();
	foreach ($starwebXml->products->product as $oXmlProduct) {rr
		foreach ($oXmlProduct->models->model as $oXmlModel) {
			$allProduct[str_replace('P', '', $oXmlModel['id'])] = $oXmlProduct;
		}
	}
	// Get xls file
	$objPHPExcel = PHPExcel_IOFactory::load("supplier.xls");
	// Find correct columns
	$colArtecleNo = 'C';
	$colUtprisEx = 'H';
	$colInStockYesNo = 'M';
	$NoCorupt = 0;
	foreach ($objPHPExcel->getWorksheetIterator() as $worksheet) {
		foreach ($worksheet->getRowIterator() as $row) {
			$cellIterator = $row->getCellIterator();
			$cellIterator->setIterateOnlyExistingCells(true);
			$articleNo = 0;
			$price = 0;
			$stocktypeInt = '';
			foreach ($cellIterator as $cell) {
				// CODE for check columns
			}

			// CODE for check if it should be updated.

			// Do the update in starweb
			if ($update && $productId != 0) {

				unset($product['id']);
				//create xml and remove not needed things for Starweb
				$xmlData = getCleanXML($product);

				// Do a put to Starweb
				list($sResponse, $iHttpResponseCode) = restRequest('PUT', '/products/' . $productId, $xmlData);
				$responsXml = new SimpleXMLElement($sResponse);
				// Check If the XML status code indicate an error
				if ($responsXml->apiInfo->statusCode != 1) {
					$log->reportWarning(null, 'Uppdatering av: ' . $productId . ' misslyckad (' . $starwebXml->apiInfo->statusCode . ') ' . $starwebXml->apiInfo->statusMessage);
				} else {
					$log->reportInfo('Produkt: ' . $productId . ', artikel: '. $articleNo.' uppdaterad');
				}
			}
		}
	}
	echo "Uppdateringen klar";
}

/*
 * Function for cleanup the XML file, for working wih Starweb API
 * @param SimpleXMLElement $product
 * @return XML
 */
function getCleanXML($product) {
	$doc = new DOMDocument;
	$doc->preserveWhiteSpace = false;
	$doc->loadxml($product->asXML());
	$xpath = new DOMXPath($doc);
	// Remove empty atrebute
	foreach ($xpath->query('//*[not(node())]') as $node) {
		$node->parentNode->removeChild($node);
	}
	// Remove category atrebutes
	$domNodeList = $doc->getElementsByTagname('categories');
	$domElemsToRemove = array();
	foreach ($domNodeList as $domElement) {
		$domElemsToRemove[] = $domElement;
	}
	foreach ($domElemsToRemove as $domElement) {
		$domElement->parentNode->removeChild($domElement);
	}
	//Remove name in attrebute and value
	$xpath2 = new DOMXPath($doc);
	foreach ($xpath2->query('/*/*/*/*/*/name') as $node) {
		$node->parentNode->removeChild($node);
	}
	$xpath3 = new DOMXPath($doc);
	foreach ($xpath3->query('/*/*/*/*/*/*/name') as $node) {
		$node->parentNode->removeChild($node);
	}

	$doc->formatOutput = true;
	$xmlData = $doc->savexml();
	$xmlData = str_replace('<?xml version="1.0"?>', '', $xmlData);
	$xmlData = str_replace('&#xF6;', 'ö', $xmlData);
	$xmlData = str_replace('&#xE4;', 'ä', $xmlData);
	$xmlData = str_replace('&#xE5;', 'å', $xmlData);

	return $xmlData;
}

Under vården har vi hjälpt en del kunder med både nya och gamla hemsidor och de flesta har byggts i CMS-verktyget WordPress. Vi använder WordPress dels för att det är trevligt att jobba med som utvecklare, men den största fördelen är att det är så enkelt för dig som kund att själv uppdatera din hemsida efter hand, och på så sätt kan du hålla hemsidan levande utan att behöva ha någon kunnig som du betalar för att göra det.

Vi har jobbat med både små och större sidor. En mindre sida där vi gjorde designen och fixade så att kunden kom igång är Feetfashion, och kommentar från Maria på Feetfashion blev:

” Är så glad att jag anlitade Progmera och Mikael. Mycket duktig i sitt yrke, och lyhörd för mina behov! Och att han jobbar snabbt är suveränt!! Kan varmt rekommendera detta företag.”

Elegant
Sen finns det kunder där de redan har en sida med en design som de inte vill ändra, men de vill ha möjlighet att uppdatera den själva. Så var det för Zid och HSMAB och för dem behöll vi deras gamla design men byggde in det i WordPress, så att de i fortsättningen kan uppdatera informationen på hemsidan själva.

Förutom dessa mindre projekt i WordPress har vi även varit med och utvecklat Elegant och tagit fram ett plugin för deras Presentkortslösning; med allt från välja antal produkter, köpet med betalning via Klarna och diverse bakomliggande administration.

Har ni redan idag en hemsida byggd i WordPress så kan jag tipsa om vår WordPresskola på youtube.

Vi arbetar för det mesta med WordPress när vi gör nya hemsidor för våra kunder, detta för att vi tycker de är det enklaste sätt för en ovan att lära sig. Ni har väl inte missat vår WordPresskola som finns på youtube?

LinköpingsguideklubbMen vi gör inte bara nya hemsidor utan vi hjälper till att fixa med gamla hemsidor också. Vi har bland annat hjälpt Linköpingsguidklubb med migreringen från Joomla version 1.5 till 2.5 vilket är ett måste om man inte vill råka ut för tråkigheter. Tråkigheter hände det nämligen för Umeguideklubb som råkade ut för en attack, detta medförde att vi först fick rensa bort skräpkod innan vi gjorde migreringen till den nya versionen som är betydligt säkrare. Men planerar man att göra en ny hemsida i Joomla så rekommenderar vi den absolut senaste versionen som är 3.2. Joomla har dock förenklat så att man nu på samma sätt som wordpress kan uppdatera systemet med en knapptryckning mellan versionerna i 2.5 serien till 3.x serien, förutsatt att ens design stödjer det.

Förutom migreringen så blev det även till att hålla lite utbildning i hur man använder det nya systemet, något som är självklart för oss som utvecklar, eftersom det kan vara svårt och krångligt för den som inte sitter vid datorn så mycket. Så det kanske skulle vara lämpligt att hålla fler utbildningar i framtiden, både i hur man använder Joomla och WordPress?

Får önska er alla en god fortsättning på det nya året och jag hoppas ni har haft en lugn och skön jul- och nyårshelg, och är redo för ett nytt och härligt år. Här på kontoret i Linköping har det redan börjats lite under slutet av förra veckan. Det har då arbetats med egna produkter och förberedelser inför lite olika lanseringar nu i vår. Dels är det Gymsystem som kommer med en helt ny version i slutet av januari. De andra lanseringarna kommer handla om ”förmedling”, ”företagstjänster” och sen den lite svårtippade ”mode”. Så ni får verkligen hålla utkik här på bloggen eller på facebook så att ni inte missar något.

Nytt år betyder även nya möjligheter för er; driver du idag ett företag men inte syns på internet så kan jag verkligen rekommendera att ni ser över vårt erbjudande om hemsida till fast pris. För er bloggläsare kan jag nu under januari månad erbjuda 20% rabatt om ni berättar det när vi hör av oss till er efter er beställning.

God fortsättning på det nya året!

Det har jobbats en hel del med både kunduppdrag och med egna projekt. Bland kundsidorna som lanserats på senare tid har vi elegant samt bokning av halkbanan för LMS. Även ett par vanliga hemsidor till företag har tagits fram och levererats. De egna projekten har gjort stora kliv och vi kan lova minst två lanseringar kanske till och med tre stycken i vår, så då får ni hålla utkik.

Men nu får vi önska er alla en riktigt God jul och Gott nytt år!

REWORK

Dagens boktips är lite mer riktad för de som vill starta eller som driver ett företag idag, boken är REWORK av Jason Fried och David Heinemeier Hansson. Även denna bok är på Engelska men till skillnad från Code Complete så är den här boken väldigt lättläst. De försöker få dig förstå att du troligtvis inte behöver så mycket som du tror för att starta ett företag. Och att det är bättre komma ut tidigt med en bra halvklar produkt, än sent med en färdig men kass produkt, vilket helt klart är sant, men väldigt svårt då man alltid vill bli klar och göra produkten så bra som möjligt innan den når marknaden. Förutom agerandet i början av företagandet går de igenom metoder och arbetssätt under företagandets gång och även hur man kan tänka vid en tillväxt.

Jag tycker boken helt klart är läsvärd för alla som funderar på att starta företag, eller idag driver ett mindre företag. Den är så pass bra och lättläst så att ni troligtvis kan läsa igenom den på ett par kvällar, även om den är 279 sidor.

Vill ni köpa boken så hittar ni den här på Bokus. Eller se fler boktips här.

Ibland så kan man få kommentarer på nyheter eller blogginlägg, vissa är trevliga att få andra inte. Här lär ni er hur ni kan svara, redigera och ta bort kommentarer från er hemsida.