bitcoin-atm

bitcoin atm for pyc inc.

git clone https://9o.is/git/bitcoin-atm.git

App.js

(11229B)


      1 /**
      2  * Main Angular Module
      3  */
      4 var app = angular.module("app", [
      5    'ngTouch',
      6    'ui.bootstrap',
      7    'ui.mask',
      8    'mgo-angular-wizard', 
      9    'angular-qr-scanner', 
     10    'disabler',
     11    'ngKeypad'
     12 ]);
     13 
     14 /**
     15  * Detects when there's a slight touch on the screen.
     16  */
     17 app.directive('detectTap', [ '$swipe', function($swipe) {
     18 	return {
     19 		restrict : 'A',
     20 		scope : {
     21 			detectCallback : '&'
     22 		},
     23 		link : function(scope, el) {
     24 			$swipe.bind(el, {
     25 				'start' : function() {
     26 					scope.detectCallback();
     27 				}
     28 			});
     29 		}
     30 	};
     31 } ]);
     32 
     33 /**
     34  * Price Ticker shows price per bitcoin.
     35  */
     36 app.controller('FSMCtrl', ['$scope', '$rootScope', 'WizardHandler', '$window', '$timeout', function($scope, $rootScope, WizardHandler, $window, $timeout) {
     37 	
     38 	/**
     39 	 * Email regex.
     40 	 */
     41 	$rootScope.email_regex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
     42 	
     43 	/**
     44 	 * Session information is any data that was inserted into input and
     45 	 * held aside for the current session. It is cleared when transaction is over.
     46 	 */
     47 	$rootScope.session = {};
     48 	
     49 	/**
     50 	 * Contains the system's configurations such as machine name,
     51 	 * buy limit, etc.
     52 	 */
     53 	$rootScope.config = {};
     54 	
     55 	/**
     56 	 * Whether the user can go to the next screen or not.
     57 	 */
     58 	$rootScope.holdScreen = false;
     59 	
     60 	/**
     61 	 * Whether it can send the touch signal to Overlord.
     62 	 */
     63 	var canTouch = true;
     64 	
     65 	/**
     66 	 * Sends the touch signal to Overlord so it can know someone
     67 	 * touched the screen.
     68 	 */
     69 	$rootScope.touch = function() {
     70 		if(canTouch) {
     71 			$window.overlord.send("touch");
     72 			canTouch = false;
     73 			$timeout(function(){
     74 				canTouch = true;
     75 			}, 1000); 
     76 		}
     77 	};
     78 	
     79 	/**
     80 	 * Clears the current transaction session.
     81 	 */
     82 	$scope.clearSession = function() {
     83 		if($rootScope.holdScreen === false) {
     84 			$rootScope.holdScreen = true;
     85 			$timeout(function(){
     86 				$rootScope.holdScreen = false;
     87 			}, 1000); 
     88 		}
     89 		
     90 		$rootScope.fsm.data = {};
     91 		$rootScope.session = {};
     92 	};
     93 	
     94 	/**
     95 	 * Finite State Machine that remains synchronized with Overlord.
     96 	 */
     97 	$rootScope.fsm = {
     98 			initial: 'Uninitialized',
     99 			state: 'Uninitialized',
    100 			data: {},
    101 			events: [
    102 			    // Still hasn't initialized Screen
    103 			    {from: '*', to: 'Uninitialized', screen: 'Uninitialized'},
    104 
    105 			    // Idle Screen
    106 			    {from: '*', to: 'Idle', screen: 'Idle', preStart: "$scope.clearSession()"},
    107 			    
    108 			    // Malfunctioning Screen
    109 			    {from: '*', to: 'Malfunctioning', screen: 'Malfunctioning'},
    110 			    
    111 			    // Network Outage Screen
    112 			    {from: '*', to: 'NetworkOutage', screen: 'NetworkOutage'},
    113 			    
    114 			    // Error Screen
    115 			    {from: '*', to: 'ErrorState', screen: 'ErrorState'},
    116 			    {from: 'ErrorState', to: '*', postStart: "$scope.afterErrorState()"},
    117 				
    118 				// Timeout & fall back ScanQr Screen
    119 			    {from: '*', to: 'QrScan', screen: 'QrScan', preStart: "$rootScope.startScanning()"},
    120 			    {from: 'QrScan', to: '*', preStart: "$rootScope.stopScanning()"},
    121 			    {from: 'QrValidate', to: 'QrScan', preStart: "$scope.setQrError('Invalid bitcoin wallet')"},
    122 			    {from: 'UserLogin', to: 'QrScan', preStart: "$scope.setQrError('Failed connection to server')"},
    123 			    {from: 'PhoneLogin', to: 'QrScan', preStart: "$scope.setQrError('Failed connection to server')"},
    124 			    {from: 'HistoryAudit', to: 'QrScan', preStart: "$scope.setQrError('Failed to verify wallet address')"},
    125 			    {from: 'TxCreate', to: 'QrScan', preStart: "$scope.setQrError('Failed to create transaction')"},
    126 				
    127 				// Prepare Tx Screen
    128 				{from: 'QrScan', to: 'QrValidate', screen: 'PreparingTx'},
    129 				{from: 'QrValidate', to: 'UserLogin', screen: 'PreparingTx'},
    130 				{from: 'UserLogin', to: 'PhoneLogin', screen: 'PreparingTx'},
    131 				{from: 'UserLogin', to: 'HistoryAudit', screen: 'PreparingTx'},
    132 				{from: 'PhoneLogin', to: 'HistoryAudit', screen: 'PreparingTx'},
    133 				{from: 'HistoryAudit', to: 'TxCreate', screen: 'PreparingTx'},
    134 				
    135 				// Cash Insert
    136 				{from: '*', to: 'CashInsert', screen: 'CashInsert'},
    137 				{from: '*', to: 'CashValidate', screen: 'CashInsert'},
    138 				
    139 				// Insufficient Funds
    140 				{from: '*', to: 'InsufficientFunds', screen: 'InsufficientFunds'},
    141 				
    142 				// Limit Reached Screen
    143 				{from: '*', to: 'LimitReached', screen: 'LimitReached'},
    144 				{from: 'LimitReached', to: '*', postStart: "$scope.afterLimitReached()"},
    145 				
    146 				// Sending Bitcoins Screen
    147 				{from: 'CashInsert', to: 'Sending', screen: 'Sending'},
    148 				
    149 				// Thank You Screen
    150 				{from: 'Sending', to: 'ThankYou', screen: 'ThankYou'},
    151 				
    152 				// receipt screen
    153 				{from: 'ThankYou', to: 'Receipt', screen: 'Receipt', postStart: "$scope.beforeReceipt()"},
    154 				{from: 'Receipt', to: '*', postStart: "$scope.afterReceipt()"},
    155 				
    156 				// Ad
    157 				{from: '*', to: 'Ad', screen: 'Ad', postStart: "$('#ad-overlay').addClass('roll-out')"},
    158 				{from: 'Ad', to: '*', postStart: "$('#ad-overlay').removeClass('roll-out')"}
    159 			]
    160 	};
    161 	
    162 	/**
    163 	 * Function executes when there is a state transition.
    164 	 */
    165 	var transition = function(from, to) {
    166 		
    167 		var events = _.filter($rootScope.fsm.events, function(e){
    168 			return (e.from === from || e.from === "*") && (e.to === to || e.to === "*"); 
    169 		});
    170 				
    171 		_.each(events, function(e){
    172 			if(e.hasOwnProperty('preStart')) {
    173 				eval(e.preStart);
    174 			}
    175 		});
    176 		
    177 		var event = _.find(events, function(e){
    178 			return e.hasOwnProperty('screen');
    179 		});
    180 		
    181 		if(typeof event === 'undefined') {
    182 			$window.console.error("State transition event not found: "+from+" -> "+to);
    183 			return;
    184 		}
    185 		
    186 		if(event.hasOwnProperty('screen')) {
    187 			WizardHandler.wizard().goTo(event.screen);
    188 		}
    189 
    190 		$rootScope.fsm.state = to;
    191 		
    192 		_.each(events, function(e){
    193 			if(e.hasOwnProperty('postStart')) {
    194 				eval(e.postStart);
    195 			}
    196 		});
    197 	};
    198 	
    199 	/**
    200 	 * Event that updates the client-side FSM data.
    201 	 */
    202 	$rootScope.$on("dataUpdate", function (event, message) {
    203 		$rootScope.fsm.data = angular.copy(message);
    204 	});
    205 	
    206 	/**
    207 	 * Event that updates the client-side FSM data.
    208 	 */
    209 	$rootScope.$on("stateUpdate", function (event, message) {
    210 		var from = ((message.from) ? message.from : $rootScope.fsm.state);
    211 		var to = ((message.to) ? message.to : message);
    212 		transition(from, to);
    213 	});
    214 	
    215 	/**
    216 	 * Event that updates the client-side price ticker.
    217 	 */
    218 	$rootScope.$on("priceUpdate", function (event, message) {
    219 		$rootScope.price = message;
    220 	});
    221 	
    222 	/**
    223 	 * Updates the client-side configurations.
    224 	 */
    225 	$rootScope.$on("configUpdate", function (event, message) {
    226 		$rootScope.config = message;
    227 	});
    228 	
    229 	/**
    230 	 * Updates the client-side session data.
    231 	 */
    232 	$rootScope.$on("sessionUpdate", function (event, message) {
    233 		jQuery.extend($rootScope.session, message);
    234 	});
    235 	
    236 	/**
    237 	 * Sends the continue command to Overlord to
    238 	 * possibly go to the next screen.
    239 	 */
    240 	$rootScope.nextScreen = function(wznext) {
    241 		if($rootScope.holdScreen === false) {
    242 			$window.overlord.send("continue");
    243 			
    244 			if(wznext) {
    245 				WizardHandler.wizard().next();
    246 			}
    247 		}
    248 	};
    249 	
    250 	/**
    251 	 * Sends the previous command to Overlord to
    252 	 * possibly go to the previous screen.
    253 	 */
    254 	$rootScope.previousScreen = function(wzprevious) {
    255 		if($rootScope.holdScreen === false) {
    256 			$window.overlord.send("previous");
    257 			
    258 			if(wzprevious) {
    259 				WizardHandler.wizard().previous();
    260 			}
    261 		}
    262 	};
    263 	
    264 	/**
    265 	 * Sends the buy command to Overlord to complete the transaction
    266 	 * and send the bitcoins.
    267 	 */
    268 	$rootScope.buy = function() {
    269 		$window.overlord.send("buy");
    270 	};
    271 	
    272 	/**
    273 	 * Sets error message that occurred before 
    274 	 * transaction was created or while scanning 
    275 	 * QR code.
    276 	 */
    277 	$scope.setQrError = function(msg) {
    278 		$rootScope.qrError = true;
    279 		$rootScope.qrErrorMsg = msg;
    280 		$timeout(function() {
    281 			$rootScope.qrError = false;
    282   		}, 6000);
    283 	};
    284 	
    285 	/**
    286 	 * Displays input screen given the input name.
    287 	 * Inputs are available in /templates-hidden/input directory.
    288 	 */
    289 	$rootScope.unwrapInput = function(name, animate) {
    290 		$("#"+name).removeClass("hidden");
    291 		
    292 		if(typeof animate === 'undefined' || animate) {
    293 			$("#"+name).addClass("animated fadeIn");
    294 			$("#"+name+" .input-container").addClass("animated zoomIn");
    295 			$("#"+name+" button").addClass("animated zoomIn");
    296 		} else {
    297 			$("#"+name).css("opacity", "1");
    298 		}
    299 		
    300 		$("#"+name+" input").focus();
    301 	};
    302 	
    303 	/**
    304 	 * Hides input screen given the input name.
    305 	 */
    306 	$rootScope.wrapInput = function(name) {
    307 		$("#"+name).addClass("hidden");
    308 		$("#"+name).removeClass("animated fadeIn");
    309 		$("#"+name+" .input-container").removeClass("animated zoomIn");
    310 		$("#"+name+" button").removeClass("animated zoomIn");
    311 	};
    312 	
    313 	/**
    314 	 * Runs before receipt screen is shown.
    315 	 */
    316 	$scope.beforeReceipt = function() {
    317 		$('#receipt-holder').addClass('print');
    318 	};
    319 	
    320 	/**
    321 	 * Runs after receipt screen is shown.
    322 	 */
    323 	$scope.afterReceipt = function() {
    324 		$('#receipt-holder').removeClass('print');
    325 		$rootScope.wrapInput('receipt-email');
    326 		$rootScope.loading = false;
    327 	};
    328 	
    329 	/**
    330 	 * Runs after limit reached screen is shown.
    331 	 */
    332 	$scope.afterLimitReached = function() {
    333 		$rootScope.wrapInput('verify-phone');
    334 		$rootScope.wrapInput('verify-sms');
    335 		$rootScope.loading = false;
    336 	};
    337 	
    338 	/**
    339 	 * Runs after error state screen is shown.
    340 	 */
    341 	$scope.afterErrorState = function() {
    342 		$rootScope.wrapInput('error-phone');
    343 		$rootScope.wrapInput('error-email');
    344 		$rootScope.loading = false;
    345 	};
    346 	
    347 	/**
    348 	 * Sends receipt email.
    349 	 */
    350 	$rootScope.sendReceipt = function() {
    351 		$window.overlord.send({email: $rootScope.session.email});
    352 		$rootScope.loading = true;
    353 	};
    354 	
    355 	/**
    356 	 * Sends customer support text message.
    357 	 */
    358 	$rootScope.sendPhoneError = function() {
    359 		$window.overlord.send({phone: $rootScope.session.phone});
    360 		$rootScope.loading = true;
    361 	};
    362 	
    363 	/**
    364 	 * Sends customer support email.
    365 	 */
    366 	$rootScope.sendEmailError = function() {
    367 		$window.overlord.send({email: $rootScope.session.email});
    368 		$rootScope.loading = true;
    369 	};
    370 	
    371 	/**
    372 	 * Sends text message to phone number to verify.
    373 	 */
    374 	$rootScope.sms = function() {
    375 		$window.overlord.send({phone: $rootScope.session.phone});
    376 		$rootScope.wrapInput('verify-phone');
    377 		$rootScope.unwrapInput('verify-sms', false);
    378 	};
    379 	
    380 	/**
    381 	 * Checks if submitted SMS code matches the authenticated code.
    382 	 */
    383 	$rootScope.verifyPhone = function() {
    384 		if($rootScope.session.input_smscode === $rootScope.session.smscode) {
    385 			$window.overlord.send({phone: $rootScope.session.phone, verified: true});
    386 		} else {
    387 			$rootScope.session.wrong_smscode = true;
    388 			$rootScope.session.input_smscode = "";
    389 			$("#verify-sms input").focus();
    390 		}
    391 	};
    392 	
    393 	
    394 	$timeout(function(){
    395 		
    396 		// register anything here
    397 		
    398 	}, 4000);
    399 }]);
    400 
    401 
    402 app.controller('IdleCtrl', ['$scope', '$rootScope', '$timeout', function($scope, $rootScope, $timeout) {
    403 	
    404 	var animations = ['wobble', 'tada', 'swing', 'shake', 'bounce'];
    405 	
    406 	var animate = function() {
    407 		var animation = animations[Math.floor(Math.random()*animations.length)];
    408 		document.getElementById("bitcoin").className = 'animated '+animation;
    409 		$timeout(animate, 60000);
    410 	};
    411 	
    412 	var actions = ['BUY', 'WANT', 'NEED', 'GET', 'GO', 'DESIRE'];
    413 	
    414 	var newAction = function() {
    415 		$rootScope.bitcoinAction = actions[Math.floor(Math.random()*actions.length)];
    416 		$timeout(newAction, 60000 * 10); // every 10 minutes
    417 	};
    418 	
    419 	newAction();
    420 	$timeout(animate, 60000);
    421 }]);