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 }]);