├── .ackrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── boot.php ├── composer.json ├── composer.lock ├── doc └── index.ad ├── etc ├── Doxyfile ├── apache2-example.conf ├── config-example.php ├── receipt-foot.txt ├── receipt-head.txt ├── receipt-tail.txt └── uom.ini ├── feature └── POS.feature ├── lib ├── B2C │ ├── Sale.php │ └── Sale │ │ └── Item.php ├── Cart.php ├── Controller │ ├── API │ │ ├── B2C.php │ │ ├── B2C │ │ │ ├── Item.php │ │ │ ├── Payment.php │ │ │ ├── Receipt.php │ │ │ └── Single.php │ │ ├── Base.php │ │ └── Main.php │ ├── Auth │ │ ├── Init.php │ │ ├── Shut.php │ │ └── oAuth2 │ │ │ ├── Back.php │ │ │ └── Open.php │ ├── CRM │ │ ├── Contact.php │ │ ├── Main.php │ │ └── Message.php │ ├── Contact.php │ ├── Dashboard │ │ └── Main.php │ ├── Intent.php │ ├── Menu │ │ └── Main.php │ ├── POS │ │ ├── Ajax.php │ │ ├── Cart │ │ │ ├── Ajax.php │ │ │ ├── Drop.php │ │ │ ├── Open.php │ │ │ └── Save.php │ │ ├── Checkout │ │ │ ├── Commit.php │ │ │ ├── Done.php │ │ │ ├── Open.php │ │ │ └── Receipt.php │ │ ├── Delivery.php │ │ ├── Main.php │ │ ├── Online.php │ │ └── Shut.php │ ├── Report │ │ ├── Ajax.php │ │ └── Main.php │ └── Shop │ │ ├── Base.php │ │ ├── Cart.php │ │ ├── Checkout.php │ │ ├── Example.php │ │ └── Main.php ├── Inventory.php ├── Middleware │ └── Auth.php ├── Module │ ├── API.php │ ├── B2B.php │ ├── CRM.php │ ├── Dashboard.php │ ├── Menu.php │ ├── POS.php │ ├── Report.php │ ├── Shop.php │ └── Webhook.php ├── PDF │ ├── Base.php │ └── Receipt.php ├── Product.php ├── Product │ └── Type.php ├── Service │ └── Weedmaps.php └── Traits │ └── OpenAuthBox.php ├── make.sh ├── package-lock.json ├── package.json ├── sass ├── _base.scss ├── _pos-terminal.scss └── main.scss ├── test ├── Base.php ├── Core │ ├── Config_Test.php │ ├── Core_Test.php │ └── System_Test.php ├── Unit │ └── API_Test.php ├── Webhook │ └── Weedmaps_Test.php ├── phpunit.xml.dist └── test.php ├── view ├── _block │ ├── body-head.php │ ├── inventory-list.php │ ├── modal.php │ ├── modal │ │ └── pos │ │ │ ├── card-swipe.php │ │ │ ├── cart-options.php │ │ │ ├── customer-info.php │ │ │ ├── discount.php │ │ │ ├── hold.php │ │ │ ├── keypad.php │ │ │ ├── loyalty.php │ │ │ ├── payment-card.php │ │ │ ├── payment-cash.php │ │ │ ├── scan-id.php │ │ │ └── transaction-limit.php │ └── session-flash.php ├── _layout │ ├── html-pos.php │ ├── html.php │ └── shop-html.php ├── contact │ └── update.php ├── crm │ ├── contact.php │ ├── main.php │ ├── message-compose-email.php │ ├── message-compose-sms.php │ └── message.php ├── dashboard.php ├── done.php ├── intent │ ├── delivery-auth.php │ ├── main.php │ └── vendor-view.php ├── pos │ ├── checkout │ │ ├── done.php │ │ └── receipt.php │ ├── contact-select.php │ ├── contact-verify.php │ ├── delivery.php │ ├── main.php │ ├── online.php │ ├── open.php │ └── terminal │ │ ├── main.php │ │ └── shut.php ├── report │ └── recent.php └── shop │ ├── cart.php │ ├── checkout-done.php │ ├── checkout.php │ ├── example.php │ └── main.php └── webroot ├── .well-known └── openthc │ ├── app │ └── pos ├── css └── shop.css ├── index.html ├── js ├── pos-camera.js ├── pos-cart.js ├── pos-modal-cart-options.js ├── pos-modal-discount.js ├── pos-modal-loyalty.js ├── pos-modal-payment.js ├── pos-printer.js ├── pos-scanner.js └── pos.js ├── loading.html ├── main.php └── robots.txt /.ackrc: -------------------------------------------------------------------------------- 1 | # 2 | # OpenTHC .ackrc 3 | # 4 | 5 | --ignore-dir=.bundle/ 6 | --ignore-dir=.cache/ 7 | --ignore-dir=.composer/ 8 | --ignore-dir=.git/ 9 | --ignore-dir=.idea/ 10 | --ignore-dir=.local/ 11 | --ignore-dir=.npm/ 12 | --ignore-dir=.vscode/ 13 | --ignore-dir=doxygen/ 14 | --ignore-dir=log-archive/ 15 | --ignore-dir=node_modules/ 16 | --ignore-dir=output-data/ 17 | --ignore-dir=public/ 18 | --ignore-dir=source-data/ 19 | --ignore-dir=tmp/ 20 | --ignore-dir=var/ 21 | --ignore-dir=vendor/ 22 | --ignore-dir=webroot/assets/ 23 | --ignore-dir=webroot/css/ 24 | --ignore-dir=webroot/doc/ 25 | --ignore-dir=webroot/img/ 26 | --ignore-dir=webroot/lib/ 27 | --ignore-dir=webroot/openapi-ui/ 28 | --ignore-dir=webroot/output/ 29 | --ignore-dir=webroot/pub/ 30 | --ignore-dir=webroot/redoc/ 31 | --ignore-dir=webroot/sdk/ 32 | --ignore-dir=webroot/test-output/ 33 | --ignore-dir=webroot/vendor/ 34 | 35 | 36 | --ignore-file=is:.phpunit.result.cache 37 | --ignore-file=is:composer.lock 38 | --ignore-file=is:package-lock.json 39 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | ; 12 | ; JSON files 13 | [*.json] 14 | indent_size = 4 15 | indent_style = tab 16 | 17 | ; 18 | ; YAML files 19 | [*.{yml,yaml}] 20 | indent_size = 2 21 | indent_style = space 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # OpenTHC .gitignore 3 | # 4 | 5 | **/.idea/workspace.xml 6 | **/.idea/tasks.xml 7 | *.phar 8 | .build/ 9 | .config/ 10 | .env* 11 | .gradle/ 12 | .idea/ 13 | .local/ 14 | .netlify/ 15 | .vscode/ 16 | bower_components/ 17 | build/ 18 | Custom/ 19 | etc/ 20 | node_modules/ 21 | public/ 22 | release/ 23 | source-data/ 24 | test/*.xsl 25 | test/phpunit.xml 26 | var/ 27 | vendor/ 28 | webroot/pub/ 29 | webroot/test-output/ 30 | webroot/vendor/ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cannabis Retail Software 2 | 3 | It's a pretty simple web-based point-of-sale, customer loyalty, online-menu system. 4 | IT can be used stand-alone or integrated with BioTrack, CCRS or Metrc. 5 | 6 | ## Installation 7 | 8 | git clone {this repo} 9 | ./make.sh install 10 | [ update apache config ] 11 | certbot ... 12 | [ update apache config for ssl ] 13 | 14 | Once the system is installed you'll have to configure it for your environment. 15 | That is, connect to the government CRE if you have one - configure license information, etc. 16 | 17 | 18 | ## Features 19 | 20 | * Point of Sale (POS) 21 | * Instore Signage (ChromeCast, Tablet) 22 | * Online Ordering, In-Store Kiosk 23 | * Customer Loyalty Program (CRM) 24 | 25 | 26 | ## Point of Sale 27 | 28 | * Auto-Print Pick Ticket 29 | * Save/Transfer Sale in Progress (Cart) 30 | * Customizable Receipt 31 | * Android and iOS Compatible (coming soon) 32 | 33 | 34 | ## Loyalty Program 35 | 36 | * SMS Campaigns 37 | * Email Loyalty Campaign 38 | * Participant Facets: Revenue, Specials, Concentrate, Edible, Flower, etc 39 | 40 | 41 | ## Vendor Management System 42 | 43 | Allows a Retail business to create their own Vendor Portal 44 | 45 | Vendor can visit and see when their Sample Day is / could be, sees instructions, request a time to stop by. 46 | Vendor can be rejected with a simple button press. 47 | 48 | 49 | ## Vendor Database 50 | 51 | Just supplemental data on the main dir.openthc.com 52 | 53 | 54 | ## Relationships 55 | 56 | Calendar, Terms, Vendor Notes 57 | 58 | 59 | ## Sample Processing 60 | 61 | Sample Calendar, Vendors Feedback 62 | 63 | 64 | ## Joint Inventory 65 | 66 | Shared Inventory between Retailer and Supplier 67 | 68 | 69 | ## Alternatives 70 | 71 | In case free & open-source software is not to your taste, 72 | or maybe you're just looking for more information, 73 | here are other offerings. 74 | 75 | 76 | ### Point of Sale 77 | 78 | * BioTrackTHC - https://www.biotrack.com/ 79 | * Cova - https://www.covasoftware.com/pos 80 | * GreenBits - https://www.greenbits.com/ 81 | * KlickTrack - https://getklicktrack.io 82 | 83 | 84 | ### Loyalty Offering 85 | 86 | * 420 Solutions - https://www.4twentysolutions.com/ 87 | * CannaReward - http://www.cannareward.com/ 88 | * SpringBig - https://www.springbig.com/ 89 | * SproutCannabis - http://www.sprout.online 90 | * TokeIn - https://tokein.com/ 91 | 92 | 93 | ### Online Ordering 94 | 95 | * Eaze - https://www.eaze.com/ 96 | * WoahStork - https://www.woahstork.com/ 97 | 98 | 99 | ### Hardware 100 | 101 | * [ELO Touch](https://www.elotouch.com/pos-terminals-paypoint-plus-for-windows.html) 102 | * [Heckler](https://hecklerdesign.com/) 103 | -------------------------------------------------------------------------------- /boot.php: -------------------------------------------------------------------------------- 1 | Invalid Application Configuration [POS-017]', 500); 22 | } 23 | 24 | define('OPENTHC_SERVICE_ID', \OpenTHC\Config::get('openthc/pos/id')); 25 | define('OPENTHC_SERVICE_ORIGIN', \OpenTHC\Config::get('openthc/pos/origin')); 26 | 27 | _error_handler_init(); 28 | 29 | /** 30 | * Database Connection 31 | */ 32 | function _dbc($dsn=null) 33 | { 34 | static $dbc_list = []; 35 | 36 | if (empty($dsn)) { 37 | $dsn = 'auth'; 38 | } 39 | 40 | $dbc = $dbc_list[$dsn]; 41 | if (!empty($dbc)) { 42 | return $dbc; 43 | } 44 | 45 | switch ($dsn) { 46 | case 'auth': 47 | case 'main': 48 | case 'root': 49 | 50 | $cfg = \OpenTHC\Config::get(sprintf('database/%s', $dsn)); 51 | if (empty($cfg)) { 52 | _exit_text('Invalid Database Configuration [ABD-039]', 500); 53 | } 54 | 55 | $dbc_list[$dsn] = new \Edoceo\Radix\DB\SQL(sprintf('pgsql:application_name=openthc-pos;host=%s;dbname=%s', $cfg['hostname'], $cfg['database']), $cfg['username'], $cfg['password']); 56 | 57 | return $dbc_list[$dsn]; 58 | 59 | default: 60 | 61 | $dbc_list[$dsn] = new \Edoceo\Radix\DB\SQL($dsn); 62 | 63 | return $dbc_list[$dsn]; 64 | 65 | } 66 | 67 | return null; 68 | } 69 | 70 | function _draw_html_card($head, $body, $foot=null) 71 | { 72 | ob_start(); 73 | echo '
'; 74 | printf('
%s
', $head); 75 | printf('
%s
', $body); 76 | if ($foot) { 77 | printf('', $foot); 78 | } 79 | echo '
'; 80 | 81 | return ob_get_clean(); 82 | } 83 | 84 | /** 85 | * You can put custom stuff here, it will be available to the entire application 86 | */ 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openthc/pos", 3 | "description": "Cannabis Retail Point of Sale", 4 | "license": "GPL-3.0-only", 5 | "version": "420.24.326", 6 | "autoload": { 7 | "exclude-from-classmap": [ 8 | "vendor/erusev/parsedown-extra/test" 9 | ], 10 | "psr-4": { 11 | "OpenTHC\\POS\\": "./lib", 12 | "OpenTHC\\POS\\Test\\": "./test" 13 | } 14 | }, 15 | "require": { 16 | "openthc/common": "dev-main", 17 | "openthc/cre-adapter": "dev-main", 18 | "pear/net_smtp": "1.10.0", 19 | "tuupola/base32": "2.0.0", 20 | "tecnickcom/tcpdf": "6.9.0" 21 | }, 22 | "require-dev": { 23 | "php-webdriver/webdriver": "1.15.2", 24 | "phpmd/phpmd": "2.15.0", 25 | "phpstan/phpstan": "2.1.11", 26 | "phpunit/phpunit": "9.6.22" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /doc/index.ad: -------------------------------------------------------------------------------- 1 | :toc: left 2 | :doctype: book 3 | :source-highlighter: pygments 4 | :pygments-style: rainbow_dash 5 | :icons: font 6 | :revealjsdir: https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.8.0 7 | 8 | 9 | = OpenTHC POS 10 | 11 | The OpenTHC POS Package is a web-based point of sale integrated with BioTrack, LeafData and METRC. 12 | 13 | == Installation 14 | 15 | Clone the repo onto your web-platform. 16 | This can be a remote host, or a computer running on-site, it has been known to operate from a Raspberry Pi. 17 | 18 | Connect the system to a database, a schema is not included here, one is available from the CRE package. 19 | 20 | Update the configuration to connect to the proper CRE. 21 | 22 | Process sales! 23 | 24 | 25 | == Overview 26 | 27 | === Retail Management System 28 | 29 | * Point-of-Sale/Terminal 30 | * Compliance Reporting Engine Integration (BT, LD, M) 31 | * Product Intake 32 | * ID Verification 33 | * Sales Limits 34 | * Discounts 35 | * Customer Checkin 36 | * Loyalty Programs 37 | * Online Ordering and Menus 38 | 39 | 40 | == Requirements 41 | 42 | Runs on Android tables, Apple iPad, Desktop. 43 | Linux and Windows. 44 | 45 | Handheld Scanner 46 | - Any 47 | -------------------------------------------------------------------------------- /etc/apache2-example.conf: -------------------------------------------------------------------------------- 1 | # 2 | # pos.openthc 3 | # 4 | 5 | Define "pos_host" "pos.openthc.example.com" 6 | Define "pos_root" "/opt/openthc/pos" 7 | 8 | 9 | # webroot 10 | 11 | 12 | 13 | Require all denied 14 | 15 | 16 | AllowOverride None 17 | Options FollowSymLinks Indexes 18 | Require all granted 19 | 20 | # Headers 21 | Header set referrer-policy "same-origin" 22 | Header set x-content-type-options "nosniff" 23 | Header set x-frame-options "sameorigin" 24 | Header set x-xss-protection "1; mode=block" 25 | 26 | # Main Controller 27 | RewriteEngine On 28 | RewriteCond %{REQUEST_FILENAME} !-d 29 | RewriteCond %{REQUEST_FILENAME} !-f 30 | RewriteRule .* /main.php [L,QSA] 31 | 32 | # PHP Settings 33 | php_flag allow_url_fopen off 34 | php_flag allow_url_include off 35 | php_flag define_syslog_variables on 36 | php_flag display_errors off 37 | php_flag display_startup_errors off 38 | php_flag enable_dl off 39 | php_flag error_log on 40 | php_flag expose_php off 41 | php_flag html_errors off 42 | php_flag ignore_repeated_errors on 43 | php_flag ignore_repeated_source on 44 | php_flag implicit_flush off 45 | php_flag log_errors on 46 | php_flag magic_quotes_runtime off 47 | php_flag mail.add_x_header off 48 | 49 | php_value date.timezone UTC 50 | php_value error_reporting -1 51 | php_value max_execution_time 300 52 | php_value max_input_vars 1024 53 | php_value memory_limit 256M 54 | php_value post_max_size 48M 55 | php_value upload_max_filesize 32M 56 | 57 | # Session Data 58 | php_flag session.auto_start off 59 | php_flag session.cookie_httponly on 60 | php_flag session.cookie_secure on 61 | php_flag session.use_strict_mode on 62 | php_value session.cookie_lifetime 14400 63 | php_value session.cookie_samesite lax 64 | php_value session.gc_maxlifetime 3600 65 | php_value session.name openthc 66 | 67 | 68 | 69 | 70 | # 71 | # HTTP 72 | 73 | 74 | ServerName ${pos_host} 75 | DocumentRoot ${pos_root}/webroot 76 | 77 | RewriteEngine On 78 | RewriteCond %{HTTPS} !=on 79 | RewriteRule ^/.well-known - [END] 80 | RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L] 81 | 82 | 83 | 84 | 85 | # 86 | # HTTPS 87 | 88 | 89 | ServerName ${pos_host} 90 | DocumentRoot ${pos_root}/webroot 91 | 92 | SSLEngine On 93 | SSLCertificateFile /etc/letsencrypt/live/${pos_host}/fullchain.pem 94 | SSLCertificateKeyFile /etc/letsencrypt/live/${pos_host}/privkey.pem 95 | 96 | # Authorization Header 97 | RewriteEngine On 98 | RewriteCond %{HTTP:Authorization} . 99 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 100 | 101 | 102 | -------------------------------------------------------------------------------- /etc/config-example.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'hostname' => 'localhost', 12 | 'username' => 'openthc_auth', 13 | 'password' => 'openthc_auth', 14 | 'database' => 'openthc_auth', 15 | ], 16 | 'main' => [ 17 | 'hostname' => 'localhost', 18 | 'username' => 'openthc_main', 19 | 'password' => 'openthc_main', 20 | 'database' => 'openthc_main', 21 | ] 22 | ]; 23 | 24 | // Redis 25 | $cfg['redis'] = [ 26 | 'hostname' => '127.0.0.1', 27 | ]; 28 | 29 | // Statsd 30 | $cfg['statsd'] = [ 31 | 'hostname' => '127.0.0.1', 32 | ]; 33 | 34 | // OpenTHC Services 35 | $cfg['openthc'] = [ 36 | 'app' => [ 37 | 'origin' => 'https://app.openthc.example', 38 | 'public' => '/* APP SERVICE PUBLIC KEY */', 39 | ], 40 | 'b2b' => [ 41 | 'origin' => 'https://b2b.openthc.example', 42 | 'public' => 'x', 43 | 'secret' => 'x', 44 | ], 45 | 'dir' => [ 46 | 'origin' => 'https://dir.openthc.example', 47 | 'public' => '/* DIR SERVICE PUBLIC KEY */', 48 | ], 49 | 'pipe' => [ 50 | 'origin' => 'https://pipe.openthc.example', 51 | 'public' => '/* PIPE SERVICE PUBLIC KEY */', 52 | ], 53 | 'pos' => [ 54 | 'id' => '/* POS SERVICE ULID */', 55 | 'origin' => 'https://pos.openthc.example', 56 | 'public' => '/* POS SERVICE PUBLIC KEY */', 57 | 'secret' => '/* POS SERVICE SECRET KEY */', 58 | ], 59 | 'sso' => [ 60 | 'origin' => 'https://sso.openthc.example', 61 | 'public' => '/* SSO SERVICE PUBLIC KEY */', 62 | 'client-id' => '/* POS SERVICE ULID */', 63 | 'client-sk' => '/* POS SERVICE SSO CLIENT SECRET KEY */' 64 | // 'scope' => 'contact company profile cre pos', 65 | ] 66 | ]; 67 | 68 | // Google 69 | $cfg['google'] = [ 70 | 'api_key_js' => '', 71 | 'map_api_key_js' => '', 72 | ]; 73 | 74 | return $cfg; 75 | -------------------------------------------------------------------------------- /etc/receipt-foot.txt: -------------------------------------------------------------------------------- 1 | *** EDIT etc/receipt-foot.txt *** 2 | 3 | ALL SALES FINAL 4 | 5 | EXCHANGES 6 | Exchanges for defective cartridges must be for similar item only. 7 | Exchanges must be made within 10 days of purchae to the same location. 8 | Exchanges must have oringal packaging and receipt. 9 | No Exceptions. 10 | 11 | WARNING: This product has intoxicating effects and may be habit forming. 12 | Cannabis can impair concentration, coordination and judgement. 13 | There may be health risks associated with consumption of this product. 14 | Should not be used by women that are pregnant or breast feeding. 15 | Smoking is hazardous to your health. 16 | Do not operate a vehicle or machinery under the influence of this drug. 17 | This product may be unlawful outside your jurisdiction. 18 | For use by adults over 21 years old. 19 | Pesticide information available upon request. 20 | 21 | KEEP OUT OF REACH OF CHILDREN 22 | 23 | *** EDIT etc/receipt-foot.txt *** 24 | -------------------------------------------------------------------------------- /etc/receipt-head.txt: -------------------------------------------------------------------------------- 1 | *** EDIT RECEIPT HEAD *** 2 | -------------------------------------------------------------------------------- /etc/receipt-tail.txt: -------------------------------------------------------------------------------- 1 | *** EDIT etc/receipt-tail.txt *** 2 | 3 | THANKS FOR SHOPPING AT $license_name 4 | 5 | 123 Fake St #4 6 | Big City, ST, 56789 7 | store@cannabis.example.com 8 | http://domain.example.com/ 9 | +1 (555) 555-5555 10 | 11 | *** EDIT etc/receipt-tail.txt *** 12 | -------------------------------------------------------------------------------- /etc/uom.ini: -------------------------------------------------------------------------------- 1 | ; Unit of Measure Data 2 | ; "name" is the Name in our System, it's key is the stub 3 | 4 | [ea] 5 | name = "Each" 6 | biotrack = true 7 | metrc = true 8 | metrc_mode = "Count" 9 | 10 | [g] 11 | name = "Grams" 12 | biotrack = true 13 | metrc = true 14 | 15 | [kg] 16 | name = "Kilograms" 17 | biotrack = true 18 | metrc = true 19 | metrc_mode = "Weight" 20 | 21 | [mg] 22 | name = "Milligrams" 23 | biotrack = true 24 | metrc = true 25 | metrc_mode = "Weight" 26 | 27 | [l] 28 | name = "Liters" 29 | biotrack = false 30 | metrc = false 31 | leafdata = false 32 | 33 | [lb] 34 | name = "Pounds" 35 | biotrack = true 36 | metrc = true 37 | metrc_mode = "Weight" 38 | 39 | 40 | ; BioTrack Supported 41 | ; METRC Not Supported 42 | [ml] 43 | name = "Milliliters" 44 | biotrack = true 45 | metrc = false 46 | 47 | 48 | [oz] 49 | name = "Ounces" 50 | biotrack = true 51 | metrc = true 52 | metrc_mode = "Weight" 53 | -------------------------------------------------------------------------------- /feature/POS.feature: -------------------------------------------------------------------------------- 1 | # 2 | # This describes the behavior of the POS application and 3 | # the basic features of the point-of-sale interface 4 | # 5 | 6 | Feature: Point of Sale 7 | 8 | In order to release product from the custody of a retail licensee 9 | Retail Contacts should be able to sell products to customers 10 | 11 | Scenario: Open the POS application from the App Dashboard 12 | Given I am a Retail Contact 13 | When I click Launch POS 14 | Then I should see the POS Dashboard 15 | 16 | Scenario: Open the POS application's Point-Of-Sale interface 17 | Given I am a Retail Contact logged into POS 18 | When I click the POS icon 19 | And I enter "1234" as my Employee Code 20 | Then I should see the point-of-sale interface 21 | 22 | Scenario: Find all active Products 23 | Given I am a Retail Contact with the POS interface open 24 | When I click the Search icon 25 | Then all active Products display in the list categorized by Product Type 26 | 27 | Scenario: Add a Product to the active Ticket 28 | Given I am a Retail Contact with the POS interface open 29 | When I click on a Product 30 | Then it is added to the Product list in the active Ticket 31 | 32 | Scenario: Add the same Product to the active Ticket 33 | Given I am a Retail Contact with one Product in the active Ticket 34 | When I click on the same Product in the active Products list 35 | Then the quantity of that Product in the active Ticket is increased by 1 36 | 37 | Scenario: Open Payment interface for an active Ticket 38 | Given I am a Retail Contact with an active Ticket worth $10.95 39 | When I press the Payment button 40 | Then I can see the payment interface 41 | 42 | Scenario: Process payment for active Ticket 43 | Given I am a Retail Contact with an active Ticket worth $10.95 44 | When I press $20 in the payment interface 45 | Then I see the $9.05 change due 46 | 47 | Scenario: Complete the sale for the active Ticket 48 | Given I am a Retail Contact that has processed all change due 49 | When I press the Complete button 50 | Then I see the Recept interface 51 | -------------------------------------------------------------------------------- /lib/B2C/Sale.php: -------------------------------------------------------------------------------- 1 | _dbc->fetchRow($sql, $arg); 27 | if (empty($res)) { 28 | $S = new Sale(); 29 | $S['id'] = $id; 30 | $S['uid'] = $_SESSION['Contact']['id']; 31 | $S['license_id'] = $License['id']; 32 | $S->save(); 33 | } else { 34 | $S = new Sale($res); 35 | } 36 | 37 | return $S; 38 | 39 | } 40 | 41 | function addItem($I, $q) 42 | { 43 | $this->_item_list[] = array( 44 | 'id' => $I['id'], 45 | 'size' => $q, 46 | ); 47 | 48 | } 49 | 50 | function getItems() 51 | { 52 | $sql = 'SELECT * FROM b2c_sale_item WHERE b2c_sale_id = ? ORDER BY sort, id'; 53 | $arg = array($this->_data['id']); 54 | $res = $this->_dbc->fetchAll($sql, $arg); 55 | return $res; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /lib/B2C/Sale/Item.php: -------------------------------------------------------------------------------- 1 | rdb = $rdb; 34 | 35 | if (empty($key)) { 36 | $key = _ulid(); 37 | } 38 | 39 | $this->id = $key; 40 | $this->key = sprintf('/%s/cart/%s', $_SESSION['License']['id'], $this->id); 41 | $this->item_list = new \stdClass(); 42 | 43 | $chk = $this->rdb->get($this->key); 44 | if ( ! empty($chk)) { 45 | $chk = json_decode($chk); 46 | foreach ($chk as $k => $v) { 47 | $this->{$k} = $v; 48 | } 49 | } 50 | 51 | } 52 | 53 | function save() 54 | { 55 | $val = json_encode($this); 56 | 57 | // $val = []; 58 | // $val['id'] = $this->id; 59 | // $val['key'] = $this->key; 60 | // $val['name'] = $this->name; 61 | // $val['Contact'] = $this->contact; 62 | // $val['item_list'] = $this->item_list; 63 | 64 | $this->rdb->set($this->key, $val, [ 'ex' => 86400 ]); 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /lib/Controller/API/B2C.php: -------------------------------------------------------------------------------- 1 | auth_parse(); 16 | 17 | $source_data = $this->parseJSON(); 18 | if (empty($source_data['license']['id'])) { 19 | return $this->failure($RES, 'Invalid License ID [A7B-016]', 400); 20 | } 21 | 22 | $dbc = _dbc($this->Company['dsn']); 23 | 24 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc); 25 | $b2c['license_id'] = $source_data['license']['id']; 26 | $b2c->save(); 27 | 28 | // Format Output 29 | $ret = $b2c->toArray(); 30 | $ret['license'] = [ 31 | 'id' => $ret['license_id'] 32 | ]; 33 | unset($ret['license_id']); 34 | 35 | __exit_json([ 36 | 'data' => $ret 37 | , 'meta' => [] 38 | ]); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/Controller/API/B2C/Item.php: -------------------------------------------------------------------------------- 1 | auth_parse(); 15 | 16 | if ( ! preg_match('/^01\w{24}$/', $ARG['id'])) { 17 | return $this->failure($RES, 'Invalid Request [ABI-015]', 400); 18 | } 19 | 20 | $source_data = $this->parseJSON(); 21 | 22 | if (empty($source_data['license']['id'])) { 23 | return $this->failure($RES, 'Invalid License ID [ABI-021]', 400); 24 | } 25 | if (empty($source_data['inventory']['id'])) { 26 | return $this->failure($RES, 'Invalid Inventory [ABI-024]', 400); 27 | } 28 | if (empty($source_data['unit_count'])) { 29 | return $this->failure($RES, 'Invalid Request [ABI-027]', 400); 30 | } 31 | 32 | 33 | $dbc = _dbc($this->Company['dsn']); 34 | 35 | // Load Sale 36 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc, $ARG['id']); 37 | if (empty($b2c['id'])) { 38 | return $this->failure($RES, 'Invalid Sale ID [ABI-032]', 400); 39 | } 40 | 41 | // Check Inventory 42 | $inv = new \OpenTHC\POS\Inventory($dbc, $source_data['inventory']['id']); 43 | if (empty($inv['id'])) { 44 | return $this->failure($RES, 'Invalid Inventory [ABI-038]', 400); 45 | } 46 | 47 | if (empty($source_data['unit_price'])) { 48 | $source_data['unit_price'] = $inv['sell']; 49 | } 50 | 51 | if ($inv['qty'] < $source_data['unit_count']) { 52 | return $this->failure($RES, 'Invalid Quantity [ABI-046]', 400); 53 | } 54 | 55 | // Add Item 56 | $b2c_item = new \OpenTHC\POS\B2C\Sale\Item($dbc); 57 | $b2c_item['b2c_sale_id'] = $b2c['id']; 58 | $b2c_item['inventory_id'] = $source_data['inventory']['id']; 59 | $b2c_item['unit_count'] = floatval($source_data['unit_count']); 60 | $b2c_item['unit_price'] = floatval($source_data['unit_price']); 61 | // $b2c_item['full_price'] = $b2c_item['unit_count'] * $b2c_item['unit_price']; 62 | $b2c_item->save(); 63 | 64 | __exit_json([ 65 | 'data' => $b2c_item->toArray() 66 | , 'meta' => [] 67 | ]); 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/Controller/API/B2C/Payment.php: -------------------------------------------------------------------------------- 1 | auth_parse(); 16 | 17 | if ( ! preg_match('/^01\w{24}$/', $ARG['id'])) { 18 | return $this->failure($RES, 'Invalid Request [ABP-015]', 400); 19 | } 20 | 21 | $source_data = $this->parseJSON(); 22 | if (empty($source_data['license']['id'])) { 23 | return $this->failure($RES, 'Invalid License ID [ABP-020]', 400); 24 | } 25 | 26 | $dbc = _dbc($this->Company['dsn']); 27 | 28 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc); 29 | if (empty($b2c['id'])) { 30 | return $this->failure($RES, 'Not Found [ABP-027]', 404); 31 | } 32 | 33 | // $b2c_ledger = new 34 | $b2c_ledger[''] 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/Controller/API/B2C/Receipt.php: -------------------------------------------------------------------------------- 1 | getItems(); 24 | foreach ($b2c_item_list as $i => $b2ci) { 25 | $b2c_item_list[$i]['Inventory'] = new \OpenTHC\POS\Inventory($dbc, $b2ci['inventory_id']); 26 | } 27 | 28 | } 29 | 30 | /** 31 | * Generate a Preview Document 32 | */ 33 | function preview($REQ, $RES, $ARG) 34 | { 35 | 36 | $this->auth_parse(); 37 | 38 | $b2c = [ 39 | 'id' => 'PREVIEW', 40 | 'created_at' => '1969-04-20T16:20:00 America/Los_Angeles', 41 | 'base_price' => 0, 42 | 'full_price' => 0, 43 | 'item_count' => 0, 44 | 'meta' => [ 45 | 'cash_incoming' => 0, 46 | 'cash_outgoing' => 0, 47 | ] 48 | ]; 49 | 50 | $max = rand(1, 10); 51 | $b2c_item_list = []; 52 | for ($idx=0; $idx<$max; $idx++) { 53 | 54 | $c = rand(1, 10); 55 | $p = rand(200, 10000) / 100; 56 | 57 | $b2c['base_price'] += ($c * $p); 58 | 59 | $b2c_item_list[] = [ 60 | 'Inventory' => [ 61 | 'guid' => ULID::create() 62 | ], 63 | 'Inventory' => [], 64 | 'Product' => [ 65 | 'name' => 'Text/Product' 66 | ], 67 | 'Variety' => [ 68 | 'name' => 'Text/Variety', 69 | ], 70 | 'unit_count' => $c, 71 | 'unit_price' => $p, 72 | 'base_price' => ($c * $p), 73 | 'full_price' => ($c * $p), 74 | ]; 75 | } 76 | 77 | $dbc = _dbc($this->Company['dsn']); 78 | 79 | $pdf = new \OpenTHC\POS\PDF\Receipt(); 80 | $pdf->setCompany( new \OpenTHC\Company($dbc, $this->Company )); 81 | $pdf->setLicense( new \OpenTHC\Company($dbc, $this->License )); 82 | // Options 83 | $pdf->head_text = json_decode($dbc->fetchOne('SELECT val FROM base_option WHERE key = :k', [ ':k' => sprintf('/%s/receipt/head', $this->License['id']) ])); 84 | $pdf->foot_text = json_decode($dbc->fetchOne('SELECT val FROM base_option WHERE key = :k', [ ':k' => sprintf('/%s/receipt/foot', $this->License['id']) ])); 85 | $pdf->foot_link = json_decode($dbc->fetchOne('SELECT val FROM base_option WHERE key = :k', [ ':k' => sprintf('/%s/receipt/link', $this->License['id']) ])); 86 | $pdf->tail_text = json_decode($dbc->fetchOne('SELECT val FROM base_option WHERE key = :k', [ ':k' => sprintf('/%s/receipt/tail', $this->License['id']) ])); 87 | 88 | $pdf->setSale($b2c); 89 | $pdf->setItems($b2c_item_list); 90 | $pdf->render(); 91 | $name = sprintf('Receipt_%s.pdf', $b2c['id']); 92 | $pdf->Output($name, 'I'); 93 | 94 | exit(0); 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/Controller/API/B2C/Single.php: -------------------------------------------------------------------------------- 1 | auth_parse(); 18 | 19 | if ( ! preg_match('/^01\w{24}$/', $ARG['id'])) { 20 | __exit_json([ 21 | 'data' => null 22 | , 'meta' => [ 'detail' => 'Invalid Request [ABS-020]' ] 23 | ], 400); 24 | } 25 | 26 | $dbc = _dbc($this->Company['dsn']); 27 | 28 | // Fetch the Specified B2C Obbject 29 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc, $ARG['id']); 30 | if (empty($b2c['id'])) { 31 | __exit_json([ 32 | 'data' => null 33 | , 'meta' => [ 'detail' => 'Not Found [ABS-029]' ] 34 | ], 404); 35 | } 36 | 37 | $b2c_item_list = $b2c->getItems(); 38 | 39 | // Format Output 40 | $ret = $b2c->toArray(); 41 | $ret['license'] = [ 'id' => $ret['license_id'] ]; 42 | unset($ret['license_id']); 43 | $ret['item_list'] = $b2c_item_list; 44 | 45 | __exit_text([ 46 | 'data' => $ret 47 | , 'meta' => [] 48 | ]); 49 | } 50 | 51 | /** 52 | * 53 | */ 54 | function commit($REQ, $RES, $ARG) 55 | { 56 | $this->auth_parse(); 57 | 58 | // Check ID 59 | if (!preg_match('/^01\w{24}$/', $ARG['id'])) { 60 | return $this->failure($RES, 'Invalid Request [ABS-055]', 400); 61 | } 62 | 63 | $source_data = $this->parseJSON(); 64 | if (empty($source_data['license']['id'])) { 65 | return $this->failure($RES, 'Invalid Request [ABS-060]', 400); 66 | } 67 | 68 | $dbc = _dbc($this->Company['dsn']); 69 | $dbc->query('BEGIN'); 70 | 71 | // Fetch the Specified B2C Obbject 72 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc, $ARG['id']); 73 | if (empty($b2c['id'])) { 74 | return $this->failure($RES, 'Not Found [ABS-063]', 404); 75 | } 76 | if (100 != $b2c['stat']) { 77 | return $this->failure($RES, 'Invalid B2C Sale State [ABS-072]', 404); 78 | } 79 | 80 | // Get Items and Decrement 81 | $sum_item_price = 0; 82 | $b2c_item_list = $b2c->getItems(); 83 | foreach ($b2c_item_list as $b2c_item) { 84 | $Inv = new \OpenTHC\POS\Inventory($dbc, $b2c_item['inventory_id']); 85 | try { 86 | $Inv->decrement($b2c_item['unit_count']); 87 | } catch (\Exception $e) { 88 | // Ignore 89 | } 90 | $sum_item_price += ($b2c_item['unit_count'] * $b2c_item['unit_price']); 91 | 92 | // Load Taxes And Add b2c_item_adjust 93 | 94 | } 95 | $b2c['base_price'] = $sum_item_price; 96 | $b2c['stat'] = 200; 97 | $b2c->save('B2C/Sale/Commit via API'); 98 | 99 | $dbc->query('COMMIT'); 100 | 101 | // @todo send to the CRE, or just add to the pump? 102 | 103 | return $RES->withJSON([ 104 | 'data' => $b2c->toArray() 105 | , 'meta' => [] 106 | ]); 107 | 108 | } 109 | 110 | /** 111 | * Verify the Transaction 112 | */ 113 | function verify($REQ, $RES, $ARG) 114 | { 115 | $this->auth_parse(); 116 | 117 | // Check ID 118 | if (!preg_match('/^01\w{24}$/', $ARG['id'])) { 119 | return $this->failure($RES, 'Invalid Request [ABS-073]', 400); 120 | } 121 | 122 | $dbc = _dbc($this->Company['dsn']); 123 | 124 | // Fetch the Specified B2C Obbject 125 | $b2c = new \OpenTHC\POS\B2C\Sale($dbc, $ARG['id']); 126 | if (empty($b2c['id'])) { 127 | return $this->failure($RES, 'Not Found [ABS-081]', 400); 128 | } 129 | 130 | // $b2c_item_list = $b2c->getItems(); 131 | 132 | // Fetch Product Type Limits 133 | // $res_product_limit = $dbc->fetchAll('SELECT * FROM b2c_sale_product_limit WHERE ') 134 | 135 | 136 | // __exit_json([ 137 | // 'data' => null 138 | // , 'meta' => [ 'detail' => 'Not Implemented [ABS-063]' ] 139 | // ], 501); 140 | 141 | return $RES->withJSON([ 142 | 'data' => null 143 | , 'meta' => [ 'detail' => 'No Limits Apply' ] 144 | ]); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /lib/Controller/API/Base.php: -------------------------------------------------------------------------------- 1 | $ERR, 25 | 'meta' => [ 'note' => 'Fatal Error [ERR-001]' ] 26 | ], 500); 27 | }; 28 | }; 29 | 30 | $c['errorHandler'] = function($c) { 31 | return function($REQ, $RES, $ERR) { 32 | 33 | $ret_code = 500; 34 | $err_code = $ERR->getCode(); 35 | if (($err_code >= 200) && ($err_code <= 599)) { 36 | $ret_code = $err_code; 37 | } 38 | 39 | __exit_json([ 40 | 'data' => $ERR, 41 | 'meta' => [ 42 | 'note' => 'Fatal Error [ERR-002]', 43 | 'error' => $ERR->getMessage(), 44 | ] 45 | ], $ret_code); 46 | }; 47 | }; 48 | 49 | // Over-ride the Response object 50 | // FAIL: Pimple\Exception\FrozenServiceException: Cannot override frozen service "response". 51 | // $c['response'] = function($c) { 52 | // $ret = new class extends \Slim\Http\Response { 53 | // function withJSON($json, $code=200, $flag=0) 54 | // { 55 | // $flag = intval($flag); 56 | // return parent::withJSON($json, $code, ($flag | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 57 | // } 58 | // }; 59 | // return $ret; 60 | // }; 61 | 62 | parent::__construct($c); 63 | 64 | } 65 | 66 | /** 67 | * This maybe should be in Middleware 68 | */ 69 | function auth_parse() 70 | { 71 | $auth = $_SERVER['HTTP_AUTHORIZATION']; 72 | if ( ! preg_match('/^Bearer v2024\/([\w\-]{43})\/([\w\-]+)$/', $auth, $m)) { 73 | __exit_json(array( 74 | 'data' => null, 75 | 'meta' => [ 'note' => 'Invalid Authorization [CAB-068]' ], 76 | ), 403); 77 | } 78 | 79 | $act = $this->open_auth_box($m[1], $m[2]); 80 | 81 | $dbc_auth = _dbc('auth'); 82 | 83 | // Find Service Lookup CPK and See if we Trust Them 84 | $Service = $this->findService($dbc_auth, $act->pk); 85 | $Contact = $this->findContact($dbc_auth, $act->contact); 86 | $Company = $this->findCompany($dbc_auth, $act->company); 87 | 88 | $dbc_user = _dbc($Company['dsn']); 89 | $License = $this->findLicense($dbc_user, $act->license); 90 | 91 | $this->Service = $Service; 92 | $this->Contact = $Contact; 93 | $this->Company = $Company; 94 | $this->License = $License; 95 | 96 | } 97 | 98 | /** 99 | * 100 | */ 101 | function failure($RES, $text, $code=500) 102 | { 103 | return $RES->withJSON([ 104 | 'data' => null 105 | , 'meta' => [ 'note' => $text ] 106 | ], $code, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 107 | } 108 | 109 | /** 110 | * 111 | */ 112 | function __invoke($REQ, $RES, $ARG) 113 | { 114 | $this->auth_parse(); 115 | 116 | __exit_json([ 117 | 'data' => $_SERVER, 118 | 'meta' => [ 'note' => 'Not Implemented [CAB-098]' ], 119 | ], 501); 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/Controller/API/Main.php: -------------------------------------------------------------------------------- 1 | fetchRow('SELECT * FROM auth_company WHERE id = :c0', [ ':c0' => $_SESSION['Company']['id'] ]); 27 | if (empty($Company0['dsn'])) { 28 | throw new \Exception('Fatal Database Error [CAC-043]', 500); 29 | } 30 | 31 | if (empty($Company0['cre'])) { 32 | throw new \Exception('Company Configuration requires CRE [CAC-030]', 500); 33 | } 34 | 35 | $dbc_user = _dbc($Company0['dsn']); 36 | $Company1 = $dbc_user->fetchRow('SELECT * FROM auth_company WHERE id = :c0', [ ':c0' => $_SESSION['Company']['id'] ]); 37 | $_SESSION['dsn'] = $Company0['dsn']; 38 | unset($Company0['dsn']); 39 | 40 | $_SESSION['Company'] = array_merge($Company0, $Company1); 41 | $_SESSION['Company']['cre_meta'] = json_decode($_SESSION['Company']['cre_meta'], true); 42 | 43 | // Load License 44 | // Maybe offer a License Picker? 45 | // return $RES->withRedirect('/auth/license/select'); 46 | if ( ! empty($_SESSION['License']['id'])) { 47 | // Reload License 48 | $_SESSION['License'] = $this->findLicense($dbc_user, $_SESSION['License']['id']); 49 | } else { 50 | // Find Default 51 | $sql = <<fetchRow($sql, [ 59 | ':f1' => 0x01000000 60 | ]); 61 | $_SESSION['License'] = $License; 62 | } 63 | 64 | // Save the CRE Stuff? 65 | $_SESSION['cre'] = \OpenTHC\CRE::getEngine($_SESSION['Company']['cre']); 66 | 67 | // Cleanup some legacy CRE data 68 | if (empty($_SESSION['cre']['license']) && !empty($_SESSION['cre']['auth']['license'])) { 69 | $_SESSION['cre']['license'] = $_SESSION['cre']['auth']['license']; 70 | unset($_SESSION['cre']['auth']['license']); 71 | } 72 | 73 | unset($_SESSION['cre']['auth']); 74 | 75 | return $RES->withRedirect($ret); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/Controller/Auth/Shut.php: -------------------------------------------------------------------------------- 1 | getStatusCode()) { 16 | return $res0; 17 | } 18 | 19 | $data = []; 20 | $data['Page'] = [ 'title' => 'Session Closed' ]; 21 | $data['body'] = '

Your session has been closed

'; 22 | $data['foot'] = 'Sign In Again'; 23 | 24 | return $RES->write( $this->render('done.php', $data) ); 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/Controller/Auth/oAuth2/Back.php: -------------------------------------------------------------------------------- 1 | getProvider(); 18 | 19 | if (empty($_GET['code'])) { 20 | __exit_text('Invalid Link [AOB-018]', 400); 21 | } 22 | 23 | // Check State 24 | $this->checkState(); 25 | 26 | $tok = null; 27 | 28 | // Try to get an access token using the authorization code grant. 29 | try { 30 | $tok = $p->getAccessToken('authorization_code', [ 31 | 'code' => $_GET['code'] 32 | ]); 33 | } catch (\Exception $e) { 34 | __exit_text('Invalid Access Token [AOB-027]', 400); 35 | } 36 | 37 | if (empty($tok)) { 38 | __exit_text('Invalid Access Token [AOB-031]', 400); 39 | } 40 | 41 | $chk = json_decode(json_encode($tok), true); 42 | if (empty($chk['access_token'])) { 43 | __exit_text('Invalid Access Token [AOB-036]', 400); 44 | } 45 | if (empty($chk['scope'])) { 46 | __exit_text('Invalid Access Token [AOB-039]', 400); 47 | } 48 | if (empty($chk['token_type'])) { 49 | __exit_text('Invalid Access Token [AOB-042]', 400); 50 | } 51 | if ('bearer' != $chk['token_type']) { 52 | __exit_text('Invalid Access Token [AOB-045]', 400); 53 | } 54 | 55 | // Using the access token, we may look up details about the 56 | // resource owner. 57 | try { 58 | 59 | $x = $p->getResourceOwner($tok)->toArray(); 60 | 61 | $_SESSION['Company'] = $x['Company']; 62 | $_SESSION['Contact'] = $x['Contact']; 63 | $_SESSION['License'] = $x['License']; 64 | $_SESSION['tz'] = $_SESSION['Company']['tz']; 65 | 66 | return $RES->withRedirect('/auth/init?' . http_build_query([ 67 | 'r' => $_GET['r'] 68 | ])); 69 | 70 | } catch (\Exception $e) { 71 | __exit_text($e->getMessage() . ' [AOB-071]', 500); 72 | } 73 | 74 | __exit_text('Invalid Request [AOB-072]', 400); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /lib/Controller/Auth/oAuth2/Open.php: -------------------------------------------------------------------------------- 1 | _get_return_path(); 29 | 30 | if ( ! empty($_GET['box'])) { 31 | 32 | $box = $_GET['box']; 33 | 34 | if (preg_match('/^v2024\/([\w\-]{43})\/([\w\-]+)$/', $box, $m)) { 35 | 36 | $this->dbc = _dbc('auth'); 37 | $act = $this->open_auth_box($m[1], $m[2]); 38 | $Service = $this->findService($this->dbc, $act->pk); 39 | $Contact = $this->findContact($this->dbc, $act->contact); 40 | $Company = $this->findCompany($this->dbc, $act->company); 41 | 42 | $_SESSION['Contact'] = $Contact; 43 | $_SESSION['Company'] = $Company; 44 | $_SESSION['License'] = [ 45 | 'id' => $act->license 46 | ]; 47 | 48 | return $RES->withRedirect('/auth/init'); 49 | 50 | } 51 | } 52 | 53 | // @deprecated 54 | if ( ! empty($_GET['jwt'])) { 55 | 56 | // $p = $this->getProvider(); 57 | $sso = new \OpenTHC\Service('sso'); 58 | $res = $sso->post('/api/jwt/verify', [ 'form_params' => [ 'token' => $_GET['jwt'] ] ]); 59 | // switch ($res['code']) { 60 | // case 200: 61 | // // OK 62 | // default: 63 | // return $RES->withJSON(['meta' => [ 'note' => 'Invalid Token [AOO-033]' ]], 400); 64 | // } 65 | 66 | $dbc = _dbc('auth'); 67 | 68 | try { 69 | 70 | $chk = JWT::decode_only($_GET['jwt']); 71 | $key = $dbc->fetchOne('SELECT hash FROM auth_service WHERE id = :s0', [ ':s0' => $chk->body->iss ]); 72 | $jwt = JWT::verify($_GET['jwt'], $key); 73 | 74 | $_SESSION['Contact'] = [ 75 | 'id' => $jwt->sub, 76 | ]; 77 | if (empty($_SESSION['Contact']['id'])) { 78 | return $RES->withJSON(['meta' => [ 'note' => 'Invalid Contact [AOO-035]' ]], 400); 79 | } 80 | 81 | $_SESSION['Company'] = [ 82 | 'id' => $jwt->company, 83 | ]; 84 | if (empty($_SESSION['Company']['id'])) { 85 | return $RES->withJSON(['meta' => [ 'note' => 'Invalid Company [AOO-042]' ]], 400); 86 | } 87 | 88 | $_SESSION['License'] = [ 89 | 'id' => $jwt->license, 90 | ]; 91 | if (empty($_SESSION['License']['id'])) { 92 | return $RES->withJSON(['meta' => [ 'note' => 'Invalid License [AOO-049]' ]], 400); 93 | } 94 | 95 | return $RES->withRedirect('/auth/init'); 96 | 97 | } catch (\Exception $e) { 98 | // What? 99 | } 100 | 101 | return $RES->withRedirect($ret_path); 102 | 103 | } 104 | 105 | $p = $this->getProvider($ret_path); 106 | $url = $p->getAuthorizationUrl([ 107 | 'scope' => 'contact company license pos', 108 | ]); 109 | 110 | $_SESSION['oauth2-state'] = $p->getState(); 111 | 112 | return $RES->withRedirect($url); 113 | 114 | } 115 | 116 | /** 117 | * 118 | */ 119 | function _get_return_path() 120 | { 121 | $ret = '/dashboard'; 122 | if (!empty($_GET['r'])) { 123 | switch ($_GET['r']) { 124 | case '1': 125 | case 'r': 126 | // @todo should validate the referrer 127 | $ret = $_SERVER['HTTP_REFERER']; 128 | break; 129 | default: 130 | $ret = $_GET['r']; 131 | break; 132 | } 133 | } 134 | 135 | return $ret; 136 | 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /lib/Controller/CRM/Contact.php: -------------------------------------------------------------------------------- 1 | array('title' => 'CRM :: Contact List'), 18 | ); 19 | 20 | $dbc = $this->_container->DB; 21 | $sql = 'SELECT * FROM contact WHERE email IS NOT NULL OR phone IS NOT NULL ORDER BY id LIMIT 50'; 22 | $data['contact_list'] = $dbc->fetchAll($sql); 23 | 24 | return $RES->write( $this->render('crm/contact.php', $data) ); 25 | 26 | } 27 | 28 | function save($REQ, $RES, $ARG) 29 | { 30 | 31 | $dbc = $this->_container->DB; 32 | 33 | $rec = [ 34 | 'id' => ULID::create(), 35 | 'fullname' => trim($_POST['contact-name']), 36 | 'email' => trim(strtolower($_POST['contact-email'])), 37 | 'phone' => trim($_POST['contact-phone']), 38 | 'hash' => '-', 39 | // 'tags' => 40 | ]; 41 | $rec['guid'] = $rec['id']; 42 | 43 | $dbc->insert('contact', $rec); 44 | 45 | return $RES->withRedirect('/crm/contact'); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/Controller/CRM/Main.php: -------------------------------------------------------------------------------- 1 | array('title' => 'CRM'), 16 | ); 17 | 18 | return $RES->write( $this->render('crm/main.php', $data) ); 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lib/Controller/CRM/Message.php: -------------------------------------------------------------------------------- 1 | array('title' => 'CRM :: Messaging'), 14 | ); 15 | 16 | return $RES->write( $this->render('crm/message.php', $data) ); 17 | 18 | } 19 | 20 | function email($REQ, $RES, $ARG) 21 | { 22 | $data = array( 23 | 'Page' => array('title' => 'CRM :: Messaging :: Compose Email'), 24 | ); 25 | 26 | return $RES->write( $this->render('crm/message-compose-email.php', $data) ); 27 | 28 | } 29 | 30 | function sms($REQ, $RES, $ARG) 31 | { 32 | $data = array( 33 | 'Page' => array('title' => 'CRM :: Messaging :: Compose SMS'), 34 | ); 35 | 36 | return $RES->write( $this->render('crm/message-compose-sms.php', $data) ); 37 | 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lib/Controller/Contact.php: -------------------------------------------------------------------------------- 1 | _container->DB; 18 | 19 | $res_contact = $dbc->fetchAll('SELECT id, fullname FROM contact WHERE guid LIKE :g0 OR code LIKE :c0', [ 20 | ':g0' => sprintf('%%%s%%', $_GET['term']), 21 | ':c0' => sprintf('%%%s%%', $_GET['term']), 22 | ]); 23 | 24 | $ret = []; 25 | foreach ($res_contact as $c) { 26 | $ret[] = [ 27 | 'id' => $c['id'], 28 | 'label' => $c['fullname'], 29 | 'value' => $c['fullname'], 30 | ]; 31 | } 32 | 33 | return $RES->withJSON($ret); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/Controller/Dashboard/Main.php: -------------------------------------------------------------------------------- 1 | array('title' => 'Dashboard'), 18 | ); 19 | 20 | $dbc_user = $this->_container->DB; 21 | $license_list = $dbc_user->fetchAll('SELECT * FROM license WHERE flag & :f1 = :f1', [ 22 | ':f1' => \OpenTHC\License::FLAG_MINE, 23 | ]); 24 | 25 | foreach ($license_list as $l) { 26 | if ('retail' == $l['type']) { 27 | $_SESSION['License'] = $l; 28 | break; 29 | } 30 | } 31 | 32 | // if (empty($_SESSION['License'])) { 33 | // return $RES->write( $this->render('pick-license.php', $data) ); 34 | // } 35 | 36 | return $RES->write( $this->render('dashboard.php', $data) ); 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /lib/Controller/Intent.php: -------------------------------------------------------------------------------- 1 | [ 'title' => 'Intent' ], 19 | ]; 20 | 21 | switch ($_GET['a']) { 22 | case 'delivery-auth': 23 | return $this->delivery_auth($RES); 24 | break; 25 | case 'vendor-view': 26 | return $RES->write( $this->render('intent/vendor-view.php', $data) ); 27 | break; 28 | } 29 | 30 | return $RES->write( $this->render('intent/main.php', $data) ); 31 | 32 | } 33 | 34 | /** 35 | * 36 | */ 37 | function delivery_auth($RES) 38 | { 39 | $data = $this->data; 40 | 41 | // $sso_link = sprintf('/auth/open?r=/') 42 | 43 | // if (empty($_POST['contact-email'])) { 44 | // return $RES->write( $this->render('intent/delivery-auth-contact.php', $data) ); 45 | // } 46 | 47 | if ('auth-code' == $_POST['a']) { 48 | 49 | // \CSRF::verify($_POST['CSRF']); 50 | 51 | // Company Database 52 | $dbc_auth = _dbc('auth'); 53 | $dsn = $dbc_auth->fetchOne('SELECT dsn FROM auth_company WHERE id = :c0', [ ':c0' => $_GET['c'] ]); 54 | if (empty($dsn)) { 55 | _exit_html_warn('

Invalid Request [LCI-025]

', 400); 56 | } 57 | 58 | // Contact Search by PIN 59 | $dbc = _dbc($dsn); 60 | $hash = md5(preg_replace('/[^\d]+/', '', $_POST['code'])); 61 | $chk = $dbc->fetchRow('SELECT id, username FROM auth_contact WHERE username = :c1 AND passcode = :p1', [ 62 | ':c1' => $_SESSION['intent-delivery-username'], 63 | ':p1' => $hash, 64 | ]); 65 | if (empty($chk['id'])) { 66 | _exit_html_warn('

Invalid Request [LCI-035]

', 400); 67 | } 68 | // We should have a cookie, with encrypted stuff? 69 | // Some kind of ID because we need to have unique passcode Too 70 | 71 | // Long-Term Marking as Delivery Auth OK? 72 | if ($_COOKIE['pos-contact']) { 73 | // Do Something Smart? 74 | } 75 | 76 | $_SESSION['Company'] = []; 77 | $_SESSION['Contact'] = []; 78 | $_SESSION['License'] = []; 79 | 80 | $_SESSION['Company']['id'] = $_GET['c']; 81 | $_SESSION['License']['id'] = $_GET['l']; 82 | $_SESSION['Contact']['id'] = $chk['id']; 83 | 84 | // $tok = $dbc_auth->insert('auth_context_ticket', [ 85 | // 'id' => _random_hash(), 86 | // 'meta' => json_encode([ 87 | // 'intent' => 'auth-open-delivery', 88 | // 'company' => $_GET['c'], 89 | // 'contact' => $chk['id'], 90 | // ]) 91 | // ]); 92 | 93 | return $RES->withRedirect(sprintf('/auth/init?_=%s&r=/delivery/live', $tok)); 94 | 95 | } 96 | 97 | return $RES->write( $this->render('intent/delivery-auth.php', $data) ); 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/Controller/Menu/Main.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openthc/pos/d54b19901e8d4e075a7a8802eaca36e1bf9ef7f6/lib/Controller/Menu/Main.php -------------------------------------------------------------------------------- /lib/Controller/POS/Cart/Drop.php: -------------------------------------------------------------------------------- 1 | _container->DB; 18 | 19 | $sql = 'DELETE FROM b2c_sale_hold WHERE id = ?'; 20 | $arg = [ $_POST['cart'] ]; 21 | $dbc->query($sql, $arg); 22 | 23 | return $RES->withJSON(null, 204); 24 | 25 | } 26 | _exit_json([ 27 | 'meta' => [ 'detail' => 'Invalid Input [PCD#023]' ], 28 | ], 400); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Controller/POS/Cart/Open.php: -------------------------------------------------------------------------------- 1 | fetchRow('SELECT * FROM b2c_sale_hold WHERE id = ?', $arg); 21 | $SH['json'] = json_decode($SH['json'], true); 22 | 23 | 24 | echo ''; 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/Controller/POS/Cart/Save.php: -------------------------------------------------------------------------------- 1 | _container->DB; 24 | 25 | $idx_item = 0; 26 | foreach ($_POST as $k => $v) { 27 | if (preg_match('/^qty\-(\w{26})$/', $k, $m)) { 28 | $idx_item++; 29 | } 30 | } 31 | if ($idx_item == 0) { 32 | return $RES->withRedirect('/pos'); 33 | } 34 | 35 | $sql = 'INSERT INTO b2c_sale_hold (contact_id, meta) VALUES (:c0, :m1) RETURNING id'; 36 | $arg = [ 37 | ':c0' => $_SESSION['Contact']['id'], 38 | // ':t1' => 'general', 39 | ':m1' => json_encode($_POST), 40 | ]; 41 | $hid = $dbc->fetchOne($sql, $arg); 42 | if (empty($hid)) { 43 | Session::flash('fail', 'Failed to place hold'); 44 | return $RES->withRedirect('/pos'); 45 | } 46 | Session::flash('info', sprintf('Hold #%d Confirmed', $hid)); 47 | 48 | if ($auto_print_ticket) { 49 | // @todo Fire & Forget and HTTP Print Request to ... ? 50 | // HTTP::post('/api/print', array('object' => 'hold', 'object-id' => $hid)); 51 | } 52 | 53 | return $RES->withRedirect('/pos'); 54 | 55 | } 56 | 57 | Session::flash('fail', 'Invalid Input [PCS-055]'); 58 | return $RES->withRedirect('/pos'); 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/Controller/POS/Checkout/Done.php: -------------------------------------------------------------------------------- 1 | array('title' => 'POS :: Checkout :: Done') 16 | ); 17 | 18 | return $RES->write( $this->render('pos/checkout/done.php', $data) ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/Controller/POS/Delivery.php: -------------------------------------------------------------------------------- 1 | ['title' => 'POS :: Delivery' ], 16 | 'b2c_sale_hold' => [], 17 | 'contact_list' => [], // Employee Contacts 18 | 'courier_list' => [], // Active Courier Contacts 19 | ]; 20 | 21 | $dbc = $this->_container->DB; 22 | 23 | // Select Couriers 24 | $sql = "SELECT id, fullname AS name FROM contact"; 25 | $data['contact_list'] = $dbc->fetchAll($sql); 26 | 27 | // Contacts are insert/delete from this table when active-on-shift 28 | $sql = "SELECT * FROM b2c_courier"; 29 | // $res = $dbc->fetchAll($sql); 30 | $data['courier_list'] = [ 31 | [ 32 | 'id' => 'ABC', 33 | 'name' => 'David Busby' 34 | , 'stat' => 200 35 | , 'ping' => 240 36 | , 'location' => '53rd & 3rd' 37 | ], 38 | [ 39 | 'id' => 'DEF', 40 | 'name' => 'Bavid Dusby' 41 | , 'stat' => 200 42 | , 'ping' => 420 43 | , 'location' => '22 Acacia Avenue' 44 | ] 45 | ]; 46 | 47 | 48 | // Select Orders 49 | $sql = <<fetchAll($sql); 57 | 58 | $data['map_api_key_js'] = \OpenTHC\Config::get('google/map_api_key_js'); 59 | 60 | return $RES->write( $this->render('pos/delivery.php', $data) ); 61 | } 62 | 63 | /** 64 | * 65 | */ 66 | function ajax($REQ, $RES, $ARG) 67 | { 68 | switch ($_GET['a']) { 69 | case 'delivery-auth': 70 | $link = sprintf('https://%s/intent?%s' 71 | , $_SERVER['SERVER_NAME'] 72 | , http_build_query([ 73 | 'a' => 'delivery-auth', 74 | 'c' => $_SESSION['Company']['id'], 75 | 'l' => $_SESSION['License']['id'] 76 | ]) 77 | ); 78 | __exit_json([ 79 | 'data' => $link 80 | ]); 81 | 82 | } 83 | 84 | __exit_json([ 85 | 'data' => null, 86 | 'meta' => [ 'detail' => 'Error' ] 87 | ], 400); 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/Controller/POS/Main.php: -------------------------------------------------------------------------------- 1 | _container->DB; 23 | 24 | $sql = << 0 30 | AND sell IS NOT NULL 31 | AND sell > 0 32 | SQL; 33 | $arg = [ 34 | ':l0' => $_SESSION['License']['id'] 35 | ]; 36 | $chk = $dbc->fetchOne($sql, $arg); 37 | if (empty($chk)) { 38 | _exit_html_fail('

Inventory Lots need to be present and priced for the POS to operate [CPH-020]

', 501); 39 | } 40 | 41 | if (empty($_SESSION['pos-terminal-id'])) { 42 | $_SESSION['pos-terminal-id'] = ULID::create(); 43 | } 44 | 45 | if (empty($_SESSION['pos-terminal-contact'])) { 46 | $data = []; 47 | $data['Page'] = [ 'title' => 'Terminal Authentication']; 48 | return $RES->write( $this->render('pos/open.php', $data) ); 49 | } 50 | 51 | // Page Data 52 | $data = array( 53 | 'Page' => array('title' => sprintf('POS :: %s %s', $_SESSION['License']['name'], $_SESSION['License']['code'])) 54 | ); 55 | 56 | $Cart = new \OpenTHC\POS\Cart($this->_container->Redis, $_GET['cart']); 57 | 58 | if (empty($Cart->Contact)) { 59 | return $RES->write( $this->render('pos/contact-select.php', $data) ); 60 | } 61 | 62 | if ($Cart->Contact->stat != Contact::STAT_LIVE) { 63 | return $RES->write( $this->render('pos/contact-verify.php', $data) ); 64 | } 65 | 66 | $data['cart'] = $Cart; 67 | 68 | return $RES->write( $this->render('pos/terminal/main.php', $data) ); 69 | 70 | } 71 | 72 | /** 73 | * POST Handler 74 | */ 75 | function post($REQ, $RES, $ARG) 76 | { 77 | switch ($_POST['a']) { 78 | case 'auth-code': 79 | 80 | // Lookup Contact by this Auth Code 81 | $code = $_POST['code']; 82 | 83 | $dbc = $this->_container->DB; 84 | 85 | $Contact = $dbc->fetch_row('SELECT * FROM auth_contact WHERE auth_code = :a0', [ 86 | ':a0' => $_POST['code'], 87 | ]); 88 | if ( ! empty($Contact['id'])) { 89 | __exit_text('Invalid Contact', 400); 90 | } 91 | 92 | // Assign to Register Session 93 | // Set Expiration in T minutes? 94 | $R = $this->_container->Redis; 95 | $k = sprintf('/%s/pos-terminal', $_SESSION['Contact']['id']); 96 | $v = $_SESSION['Contact']['id']; 97 | $R->set($k, $v, [ 'ex' => 600 ]); 98 | 99 | $_SESSION['pos-terminal-contact'] = $_SESSION['Contact']['id']; 100 | 101 | return $RES->withRedirect('/pos'); 102 | 103 | break; 104 | 105 | } 106 | 107 | } 108 | 109 | private function openCart() 110 | { 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /lib/Controller/POS/Online.php: -------------------------------------------------------------------------------- 1 | ['title' => 'POS :: Online' ], 16 | ]; 17 | $data['b2c_sale_hold'] = []; 18 | 19 | // Select 20 | $dbc = $this->_container->DB; 21 | $sql = <<fetchAll("SELECT * FROM b2c_sale_hold WHERE type = 'online'"); 30 | 31 | return $RES->write( $this->render('pos/online.php', $data) ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/Controller/POS/Shut.php: -------------------------------------------------------------------------------- 1 | _container->Redis; 23 | // $R->hset(sprintf('pos-terminal-%s', $_SESSION['pos-terminal-id']), array( 24 | // 'ping' => $_SERVER['REQUEST_TIME'], 25 | // 'name' => $_SESSION['Company']['name'], 26 | // 'stat' => 'shut', 27 | // )); 28 | 29 | $data = []; 30 | $data['Page'] = [ 31 | 'title' => 'POS :: Terminal :: Shut', 32 | ]; 33 | 34 | return $RES->write( $this->render('pos/terminal/shut.php', $data) ); 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/Controller/Report/Ajax.php: -------------------------------------------------------------------------------- 1 | _container->DB; 17 | 18 | switch ($_POST['a']) { 19 | case 'push-transaction': 20 | $B2C = $_POST['id']; 21 | $sql = 'SELECT * FROM b2c_sale WHERE id = :pk AND license_id = :l0'; 22 | $B2C = $dbc->fetchRow($sql, [ 23 | ':pk' => $B2C, 24 | ':l0' => $_SESSION['License']['id'], 25 | ]); 26 | $B2C = new \OpenTHC\POS\B2C\Sale($dbc, $B2C); 27 | if ($B2C['id']) { 28 | return $this->_push_transaction($RES, $B2C); 29 | } 30 | 31 | return $RES->withStatus(404); 32 | break; 33 | } 34 | } 35 | 36 | function _push_transaction($RES, $B2C) 37 | { 38 | $dbc = $this->_container->DB; 39 | 40 | $cfg = \OpenTHC\CRE::getEngine($_SESSION['cre']['id']); 41 | $cfg = array_merge($_SESSION['cre'], $cfg); 42 | $cre = \OpenTHC\CRE::factory($cfg); 43 | $cre->setLicense($_SESSION['License']); 44 | 45 | $meta = $B2C->getMeta(); 46 | $item_list = $B2C->getItems(); 47 | $b2c_sale_item_list = []; 48 | foreach ($item_list as $b2c_sale_item) { 49 | $I = new \OpenTHC\POS\Inventory($dbc, $b2c_sale_item['inventory_id']); 50 | $b2c_sale_item = new \OpenTHC\POS\B2C\Sale\Item($dbc, $b2c_sale_item); 51 | $b2c_sale_item_list[] = [ 52 | 'inventory' => $I, 53 | 'sale_item' => $b2c_sale_item, 54 | ]; 55 | } 56 | 57 | switch ($cre::ENGINE) { 58 | case 'metrc': 59 | 60 | $d = new \DateTime($B2C['created_at']); 61 | $obj = array( 62 | 'SalesDateTime' => $d->format(\DateTime::ISO8601), 63 | 64 | // @todo We need to have a UX for Patients and Caregivers with Identifiers 65 | // Requires: Correct Facility Type 66 | // 'SalesCustomerType' => 'Consumer', // @see GET /sales/v1/customertypes? 67 | // 'PatientLicenseNumber' => null, 68 | // 'CaregiverLicenseNumber' => null, 69 | 70 | // 'SalesCustomerType' => 'ExternalPatient', // @see GET /sales/v1/customertypes? 71 | // 'PatientLicenseNumber' => null, 72 | // 'CaregiverLicenseNumber' => null, 73 | 74 | // Requires: Patient Number 75 | // Requires: Caregiver Number 76 | // 'SalesCustomerType' => 'Caregiver', // @see GET /sales/v1/customertypes? 77 | // 'PatientLicenseNumber' => '000001', 78 | // 'CaregiverLicenseNumber' => '000002', 79 | 80 | // Requires: Patient Number 81 | 'SalesCustomerType' => 'Patient', // @see GET /sales/v1/customertypes? 82 | 'PatientLicenseNumber' => '000001', 83 | 'CaregiverLicenseNumber' => null, 84 | 85 | 'IdentificationMethod' => null, 86 | 'PatientRegistrationLocationId' => null, 87 | 'Transactions' => array(), 88 | ); 89 | foreach ($b2c_sale_item_list as $b2c_sale_item) { 90 | $sale_item = $b2c_sale_item['sale_item']; 91 | $I = $b2c_sale_item['inventory']; 92 | 93 | $uom = new \OpenTHC\UOM($sale_item['uom']); 94 | $uom = $uom->getName(); 95 | 96 | $transaction = array( 97 | 'PackageLabel' => $I['guid'], 98 | 'Quantity' => $sale_item['unit_count'], 99 | 'UnitOfMeasure' => $uom, 100 | 'TotalAmount' => ($sale_item['unit_price'] * $sale_item['unit_count']), 101 | 'UnitThcPercent' => null, 102 | 'UnitThcContent' => null, 103 | 'UnitThcContentUnitOfMeasure' => null, 104 | 'UnitWeight' => null, 105 | 'UnitWeightUnitOfMeasure' => null, 106 | ); 107 | $obj['Transactions'][] = $transaction; 108 | } 109 | 110 | $res = $cre->b2c()->create($obj); 111 | switch ($res['code']) { 112 | case 200: 113 | // $chk = $cre->b2c()->sync(); // @todo 114 | // $chk = $cre->b2c()->transactions()->sync(); // @todo 115 | // $chk = $cre->inventory()->sync(); // @todo 116 | $RES = $RES->withStatus(201) 117 | ->withHeader('Content-type', 'application/json') 118 | ->withJson([ 119 | 'data' => [ 120 | 'b2c_list' => [$B2C->toArray()] 121 | ], 122 | 'meta' => [], 123 | ]); 124 | ; 125 | break; 126 | 127 | default: 128 | \Edoceo\Radix\Session::flash('fail', $cre->formatError()); 129 | $RES = $RES->withStatus(502); 130 | } 131 | 132 | break; 133 | 134 | case 'openthc': 135 | default: 136 | } 137 | 138 | return $RES; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/Controller/Report/Main.php: -------------------------------------------------------------------------------- 1 | render('report/main.php', $data); 22 | 23 | return $RES->write($html); 24 | 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | function recent($REQ, $RES, $ARG) 31 | { 32 | $dbc = $this->_container->DB; 33 | 34 | $sql = 'SELECT * FROM b2c_sale WHERE license_id = :l0 AND stat = :s0'; 35 | $b2c_transactions = $dbc->fetchAll($sql, [ 36 | ':s0' => \OpenTHC\POS\B2C\Sale::STAT_OPEN, 37 | ':l0' => $_SESSION['License']['id'], 38 | ]); 39 | $data = []; 40 | $data['b2c_transactions'] = $b2c_transactions; 41 | 42 | $html = $this->render('report/recent.php', $data); 43 | 44 | return $RES->write($html); 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/Controller/Shop/Base.php: -------------------------------------------------------------------------------- 1 | data; 21 | $data['Page']['title'] = 'Cart'; 22 | $data['Company'] = [ 23 | 'id' => $_GET['c'], 24 | ]; 25 | 26 | $cart = sprintf('cart-%s', $data['Company']['id']); 27 | 28 | $product_list = $this->load_cart_product_list($data['Company']['id']); 29 | 30 | $data['product_list'] = []; 31 | foreach ($product_list as $p) { 32 | $data['product_list'][] = [ 33 | 'inventory' => [ 34 | 'id' => $p['inventory_id'] 35 | ] 36 | , 'product' => [ 37 | 'id' => $p['product_id'] 38 | , 'name' => $p['product_name'] 39 | , 'sell' => $p['sell'] 40 | ] 41 | , 'variety' => $p['variety'] 42 | , 'qty' => $_SESSION[$cart][ $p['inventory_id'] ] 43 | ]; 44 | } 45 | 46 | $html = $this->render('shop/cart.php', $data); 47 | 48 | return $RES->write($html); 49 | 50 | } 51 | 52 | /** 53 | * 54 | */ 55 | function post($REQ, $RES, $ARG) 56 | { 57 | switch ($_POST['a']) { 58 | case 'cart-add': 59 | 60 | $c = $_POST['company-id']; 61 | $i = $_POST['inventory-id']; 62 | 63 | $cart = sprintf('cart-%s', $c); 64 | 65 | if (empty($_SESSION[$cart])) { 66 | $_SESSION[$cart] = []; 67 | } 68 | 69 | 70 | if (empty($_SESSION[$cart][$i])) { 71 | $_SESSION[$cart][$i] = 1; 72 | } else { 73 | $_SESSION[$cart][$i]++; 74 | } 75 | 76 | __exit_json([ 77 | 'data' => [ 78 | 'count' => $_SESSION[$cart][$i], 79 | ], 80 | 'meta' => [], 81 | ]); 82 | 83 | break; 84 | 85 | case 'cart-continue': 86 | 87 | $dbc_auth = _dbc('auth'); 88 | $Company = $dbc_auth->fetchRow('SELECT id, name FROM auth_company WHERE id = :c0', [ ':c0' => $_GET['c'] ]); 89 | if (empty($Company['id'])) { 90 | _exit_html_warn('Invalid Request [CSC-064]', 400); 91 | } 92 | 93 | $cart = sprintf('cart-%s', $Company['id']); 94 | 95 | $b2b_sale = []; 96 | $b2b_sale['id'] = ULID::create(); 97 | $b2b_sale['company'] = $Company; 98 | 99 | $product_want_list = $this->load_cart_product_list($Company['id']); 100 | foreach ($product_want_list as $p) { 101 | 102 | $b2b_item = []; 103 | $b2b_item['qty'] = $_SESSION[$cart][$p['inventory_id']]; 104 | $b2b_item['inventory'] = [ 105 | 'id' => $p['inventory_id'] 106 | ]; 107 | $b2b_item['product'] = [ 108 | 'id' => $p['product_id'], 109 | 'name' => $p['product_name'], 110 | ]; 111 | $b2b_item['variety'] = [ 112 | 'id' => $p['variety_id'], 113 | 'name' => $p['variety_name'] 114 | ]; 115 | 116 | $b2b_sale['item_list'][] = $b2b_item; 117 | 118 | } 119 | 120 | $key = sprintf('b2b-sale-%s', $b2b_sale['id']); 121 | $_SESSION[$key] = $b2b_sale; 122 | 123 | return $RES->withRedirect(sprintf('/shop/checkout?o=%s', $b2b_sale['id'])); 124 | 125 | break; 126 | 127 | case 'cart-delete': 128 | 129 | $key_list = array_keys($_SESSION); 130 | foreach ($key_list as $k) { 131 | if (preg_match('/^cart/', $k)) { 132 | unset($_SESSION[$k]); 133 | } 134 | } 135 | 136 | \Edoceo\Radix\Session::flash('info', 'Cart has been emptied'); 137 | 138 | return $RES->withRedirect(sprintf('/shop?c=%s', $_GET['c'])); 139 | } 140 | 141 | } 142 | 143 | /** 144 | * 145 | */ 146 | function load_cart_product_list($c) 147 | { 148 | $dbc_auth = _dbc('auth'); 149 | $Company = $dbc_auth->fetchRow('SELECT id, name, dsn FROM auth_company WHERE id = :c0', [ ':c0' => $c ]); 150 | if (empty($Company['id'])) { 151 | _exit_html_fail('Invalid Request [CSC-121]', 400); 152 | } 153 | 154 | $dbc_user = _dbc($Company['dsn']); 155 | 156 | $sql = << 0 190 | AND inventory.id IN ({product_id_list}) 191 | ORDER BY product_type_name, product_name, package_unit_uom 192 | SQL; 193 | 194 | $cart = sprintf('cart-%s', $c); 195 | $product_want = $_SESSION[$cart]; 196 | if (empty($product_want)) { 197 | return []; 198 | } 199 | 200 | $idx = 0; 201 | $arg = []; 202 | $tmp = []; 203 | foreach ($product_want as $p => $q) { 204 | $idx++; 205 | $k = sprintf(':pw%d', $idx); 206 | $tmp[] = $k; 207 | $arg[$k] = $p; 208 | } 209 | 210 | $sql = str_replace('{product_id_list}', implode(',', $tmp), $sql); 211 | 212 | $product_list = $dbc_user->fetchAll($sql, $arg); 213 | return $product_list; 214 | 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /lib/Controller/Shop/Checkout.php: -------------------------------------------------------------------------------- 1 | data; 20 | 21 | $key = sprintf('b2b-sale-%s', $_GET['o']); 22 | $data['b2b_sale'] = $_SESSION[$key]; 23 | $data['Page']['title'] = sprintf('Checkout :: %s', $data['b2b_sale']['company']['name']); 24 | 25 | $html = $this->render('shop/checkout.php', $data); 26 | 27 | return $RES->write($html); 28 | } 29 | 30 | /** 31 | * 32 | */ 33 | function done($REQ, $RES, $ARG) 34 | { 35 | $data = $this->data; 36 | 37 | // @todo Lookup Company & Order 38 | $dbc_auth = _dbc('auth'); 39 | $Company = $dbc_auth->fetchRow('SELECT id, name, dsn FROM auth_company WHERE id = :c0', [ ':c0' => $_GET['c'] ]); 40 | if (empty($Company['id'])) { 41 | _exit_html_fail('

Invalid Request [CSC-037]

', 500); 42 | } 43 | 44 | $data['Page']['title'] = sprintf('Checkout :: %s', $Company['name']); 45 | 46 | $dbc_user = _dbc($Company['dsn']); 47 | $rec = $dbc_user->fetchRow('SELECT * FROM b2c_sale_hold WHERE id = :pk', [ ':pk' => $_GET['o'] ]); 48 | $rec['meta'] = json_decode($rec['meta'], true); 49 | $data['b2c'] = $rec['meta']; 50 | 51 | $html = $this->render('shop/checkout-done.php', $data); 52 | 53 | return $RES->write($html); 54 | 55 | } 56 | 57 | /** 58 | * 59 | */ 60 | function post($REQ, $RES, $ARG) 61 | { 62 | switch ($_POST['a']) { 63 | case 'cart-commit': 64 | 65 | $_POST['contact-email'] = filter_var($_POST['contact-email'], FILTER_VALIDATE_EMAIL); 66 | if (empty($_POST['contact-email'])) { 67 | _exit_html_warn('

Invalid Email Address [CSC-057]

Please go back and correct

', 400); 68 | } 69 | $_POST['contact-phone'] = _phone_e164($_POST['contact-phone']); 70 | 71 | 72 | $b2b = $_SESSION[sprintf('b2b-sale-%s', $_GET['o'])]; 73 | $b2b['contact'] = [ 74 | 'name' => $_POST['contact-name'], 75 | 'email' => $_POST['contact-email'], 76 | 'phone' => $_POST['contact-phone'] 77 | ]; 78 | 79 | // Company 80 | $dbc_auth = _dbc('auth'); 81 | $Company = $dbc_auth->fetchRow('SELECT id, name, dsn FROM auth_company WHERE id = :c0', [ ':c0' => $b2b['company']['id'] ]); 82 | if (empty($Company['id'])) { 83 | _exit_html_fail('

Invalid Request [CSC-063]

', 500); 84 | } 85 | 86 | $dbc_user = _dbc($Company['dsn']); 87 | unset($Company['dsn']); 88 | 89 | // Lookup Contact 90 | $chk = $dbc_user->fetchOne('SELECT id FROM contact WHERE email = :e', [ ':e' => $b2b['contact']['email'] ]); 91 | if (empty($chk)) { 92 | $chk = $dbc_user->fetchOne('SELECT id FROM contact WHERE phone = :p', [ ':p' => $b2b['contact']['phone'] ]); 93 | } 94 | if (empty($chk)) { 95 | 96 | // Search Global Directory? 97 | // $dir = new \OpenTHC\Service\OpenTHC('dir'); 98 | // $chk = $dir->get(sprintf('/api/contact/search?q=%s', rawurlencode($_POST['contact-email']))); 99 | $chk = ULID::create(); 100 | $rec = [ 101 | 'id' => $chk, 102 | 'type' => 'client', 103 | 'fullname' => $b2b['contact']['name'], 104 | 'email' => $b2b['contact']['email'], 105 | 'phone' => $b2b['contact']['phone'], 106 | ]; 107 | $rec['hash'] = md5(json_encode($rec)); 108 | $rec['guid'] = $rec['id']; 109 | $dbc_user->insert('contact', $rec); 110 | 111 | } 112 | 113 | $Contact = [ 114 | 'id' => $chk 115 | ]; 116 | 117 | // Insert POS Hold 118 | $dbc_user->insert('b2c_sale_hold', [ 119 | 'id' => $b2b['id'], 120 | 'contact_id' => $Contact['id'], 121 | 'type' => 'online', 122 | 'stat' => 100, 123 | 'meta' => json_encode($b2b), 124 | ]); 125 | 126 | unset($_SESSION[sprintf('b2b-sale-%s', $b2b['id'])]); 127 | unset($_SESSION[sprintf('cart-%s', $Company['id'])]); 128 | 129 | break; 130 | 131 | } 132 | 133 | // Redirect 134 | return $RES->withRedirect(sprintf('/shop/checkout/done?c=%s&o=%s' 135 | , $b2b['company']['id'] 136 | , $b2b['id'] 137 | )); 138 | 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /lib/Controller/Shop/Example.php: -------------------------------------------------------------------------------- 1 | data; 17 | 18 | $dbc_auth = _dbc('auth'); 19 | $Company = $dbc_auth->fetchRow('SELECT id, name, dsn FROM auth_company WHERE id = :c0', [ ':c0' => $_GET['c'] ]); 20 | if (empty($Company['id'])) { 21 | _exit_html_fail('Invalid Request [CSE-020]', 400); 22 | } 23 | 24 | $dbc_user = _dbc($Company['dsn']); 25 | unset($Company['dsn']); 26 | 27 | $data['Page']['title'] = sprintf('Example Shop :: %s', $Company['name']); 28 | $data['Company'] = $Company; 29 | 30 | $sql = << 0 65 | ORDER BY product_type_name, product_name, package_unit_uom 66 | SQL; 67 | $data['product_list'] = $dbc_user->fetchAll($sql); 68 | 69 | $html = $this->render('shop/example.php', $data); 70 | 71 | return $RES->write($html); 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/Controller/Shop/Main.php: -------------------------------------------------------------------------------- 1 | data; 14 | 15 | $dbc_auth = _dbc('auth'); 16 | $Company = $dbc_auth->fetchRow('SELECT id, name, dsn FROM auth_company WHERE id = :c0', [ ':c0' => $_GET['c'] ]); 17 | if (empty($Company['id'])) { 18 | _exit_html_fail('Invalid Request [CSE-020]', 400); 19 | } 20 | 21 | $dbc_user = _dbc($Company['dsn']); 22 | unset($Company['dsn']); 23 | 24 | $data['Page']['title'] = sprintf('Example Shop :: %s', $Company['name']); 25 | $data['Company'] = $Company; 26 | $data['product_list'] = $this->load_inventory($dbc_user); 27 | 28 | $html = $this->render('shop/example.php', $data); 29 | return $RES->write($html); 30 | } 31 | 32 | /** 33 | * 34 | */ 35 | function load_inventory($dbc_user) 36 | { 37 | $sql = << 0 72 | ORDER BY product_type_name, product_name, package_unit_uom 73 | SQL; 74 | $product_list = $dbc_user->fetchAll($sql); 75 | 76 | return $product_list; 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/Inventory.php: -------------------------------------------------------------------------------- 1 | = :d'; 17 | $arg = array( 18 | ':id' => $this->_data['id'], 19 | ':d' => $x 20 | ); 21 | $res = $this->_dbc->query($sql, $arg); 22 | if (empty($res)) { 23 | throw new \Exception("Could not Decrement Inventory"); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /lib/Middleware/Auth.php: -------------------------------------------------------------------------------- 1 | withRedirect('/auth/open'); 18 | } 19 | 20 | $RES = $NMW($REQ, $RES); 21 | 22 | return $RES; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lib/Module/API.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\API\Main'); 17 | 18 | // $a->post('/print', 'OpenTHC\POS\Controller\API\Print'); 19 | 20 | // Create Sale 21 | $a->post('/b2c', 'OpenTHC\POS\Controller\API\B2C'); 22 | 23 | $a->get('/b2c/receipt/preview', 'OpenTHC\POS\Controller\API\B2C\Receipt:preview'); 24 | 25 | $a->get('/b2c/{id}', 'OpenTHC\POS\Controller\API\B2C\Single'); 26 | 27 | $a->post('/b2c/{id}', 'OpenTHC\POS\Controller\API\B2C\Single:post'); 28 | $a->post('/b2c/{id}/item', 'OpenTHC\POS\Controller\API\B2C\Item'); 29 | 30 | $a->post('/b2c/{id}/verify', 'OpenTHC\POS\Controller\API\B2C\Single:verify'); 31 | // $a->post('/b2c/{id}/payment', 'OpenTHC\POS\Controller\API\B2C\Single:post'); 32 | $a->post('/b2c/{id}/commit', 'OpenTHC\POS\Controller\API\B2C\Single:commit'); 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /lib/Module/B2B.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\B2B\Main'); 15 | 16 | // $a->get('/incoming'); 17 | $a->get('/incoming/create', 'OpenTHC\POS\Controller\B2B\Incoming\Create'); 18 | $a->post('/incoming/create', 'OpenTHC\POS\Controller\B2B\Incoming\Create:post'); 19 | 20 | $a->map(['GET','POST'], '/sync', 'OpenTHC\POS\Controller\B2B\Sync'); 21 | // $a->map(['GET', 'POST'], '/{id}/sync', 'OpenTHC\POS\Controller\B2B\Sync'); 22 | 23 | 24 | 25 | $a->get('/{id}', 'OpenTHC\POS\Controller\B2B\View'); 26 | $a->post('/{id}', 'OpenTHC\POS\Controller\B2B\View'); 27 | 28 | $a->get('/{id}/accept', 'OpenTHC\POS\Controller\B2B\Accept'); 29 | $a->post('/{id}/accept', 'OpenTHC\POS\Controller\B2B\Accept'); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/Module/CRM.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\CRM\Main'); 15 | $a->get('/contact', 'OpenTHC\POS\Controller\CRM\Contact'); 16 | $a->post('/contact', 'OpenTHC\POS\Controller\CRM\Contact:save'); 17 | $a->get('/message', 'OpenTHC\POS\Controller\CRM\Message'); 18 | $a->get('/message/sms', 'OpenTHC\POS\Controller\CRM\Message:sms'); 19 | $a->get('/message/email', 'OpenTHC\POS\Controller\CRM\Message:email'); 20 | $a->get('/ajax', 'OpenTHC\POS\Controller\CRM\Ajax'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/Module/Dashboard.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\Dashboard\Main'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/Module/Menu.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\Menu\Main'); 15 | $a->get('/online', 'OpenTHC\POS\Controller\Menu\Online'); 16 | $a->get('/onsite', 'OpenTHC\POS\Controller\Menu\Onsite'); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /lib/Module/POS.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\POS\Main'); 18 | $a->post('', 'OpenTHC\POS\Controller\POS\Main:post'); 19 | 20 | $a->get('/fast', 'OpenTHC\POS\Controller\POS\Fast'); 21 | $a->get('/open', function($REQ, $RES, $ARG) { 22 | unset($_SESSION['Cart']); 23 | return $RES->withRedirect('/pos'); 24 | }); 25 | 26 | $a->map([ 'GET', 'POST', 'DELETE' ], '/ajax', 'OpenTHC\POS\Controller\POS\Ajax'); 27 | 28 | $a->post('/checkout/commit', 'OpenTHC\POS\Controller\POS\Checkout\Commit'); 29 | $a->get('/checkout/done', 'OpenTHC\POS\Controller\POS\Checkout\Done'); 30 | 31 | $a->get('/checkout/open', 'OpenTHC\POS\Controller\POS\Checkout\Open'); 32 | $a->post('/checkout/open', 'OpenTHC\POS\Controller\POS\Checkout\Open:post'); 33 | 34 | $a->map([ 'GET', 'POST' ], '/checkout/receipt', 'OpenTHC\POS\Controller\POS\Checkout\Receipt'); 35 | 36 | $a->post('/cart/ajax', 'OpenTHC\POS\Controller\POS\Cart\Ajax'); 37 | $a->post('/cart/drop', 'OpenTHC\POS\Controller\POS\Cart\Drop'); 38 | $a->post('/cart/save', 'OpenTHC\POS\Controller\POS\Cart\Save'); 39 | 40 | $a->get('/delivery', 'OpenTHC\POS\Controller\POS\Delivery'); 41 | $a->map([ 'GET', 'POST' ], '/delivery/ajax', 'OpenTHC\POS\Controller\POS\Delivery:ajax'); 42 | 43 | $a->get('/online', 'OpenTHC\POS\Controller\POS\Online'); 44 | 45 | $a->get('/shut', 'OpenTHC\POS\Controller\POS\Shut'); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/Module/Report.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\Report\Main'); 15 | $a->get('/b2c/recent', 'OpenTHC\POS\Controller\Report\Main:recent'); 16 | // , function($REQ, $RES, $ARG) { 17 | // return $this->view->render($RES, 'page/report/b2c/recent.html', []); 18 | // }); 19 | $a->map(['GET', 'POST'], '/ajax', 'OpenTHC\POS\Controller\Report\Ajax'); 20 | $a->get('/ajax/revenue-daily', function($REQ, $RES, $ARG) { 21 | require_once(APP_ROOT . '/lib/Controller/Report/Ajax.php'); 22 | }); 23 | $a->get('/ajax/revenue-product-type', function($REQ, $RES, $ARG) { 24 | require_once(APP_ROOT . '/lib/Controller/Report/revenue-product-type.php'); 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/Module/Shop.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\Shop\Main'); 15 | 16 | $a->get('/cart', 'OpenTHC\POS\Controller\Shop\Cart'); 17 | $a->post('/cart', 'OpenTHC\POS\Controller\Shop\Cart:post'); 18 | 19 | $a->get('/checkout', 'OpenTHC\POS\Controller\Shop\Checkout'); 20 | $a->post('/checkout', 'OpenTHC\POS\Controller\Shop\Checkout:post'); 21 | 22 | $a->get('/checkout/done', 'OpenTHC\POS\Controller\Shop\Checkout:done'); 23 | 24 | $a->get('/example', 'OpenTHC\POS\Controller\Shop\Example'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/Module/Webhook.php: -------------------------------------------------------------------------------- 1 | get('', 'OpenTHC\POS\Controller\Webhook\Main'); 20 | 21 | $a->post('/weedmaps/order', function($REQ, $RES, $ARG) { 22 | 23 | $file = sprintf('%s/var/weedmaps-order-%s.txt', APP_ROOT, ULID::create()); 24 | $json = file_get_contents('php://input'); 25 | file_put_contents($file, json_encode([ 26 | '_GET' => $_GET, 27 | '_POST' => $_POST, 28 | '_ENV' => $_ENV, 29 | '_BODY' => $json 30 | ])); 31 | 32 | $data = json_decode($json, true); 33 | switch ($data['status']) { 34 | case 'DRAFT': 35 | __exit_json($data); 36 | break; 37 | case 'PENDING': 38 | __exit_json($data); 39 | break; 40 | } 41 | 42 | __exit_json([ 43 | 'data' => null, 44 | 'meta' => [ 'note' => 'Request Not Handled' ] 45 | ], 400); 46 | 47 | }); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/PDF/Base.php: -------------------------------------------------------------------------------- 1 | setAuthor('openthc.com'); 21 | $this->setCreator('openthc.com'); 22 | //$this->setTitle($name); 23 | //$this->setSubject($name); 24 | //$this->setKeywords($this->name); 25 | 26 | // set margins 27 | $this->setMargins(0, 0, 0, true); 28 | $this->setHeaderMargin(0); 29 | $this->setPrintHeader(false); 30 | $this->setFooterMargin(0); 31 | $this->setPrintFooter(false); 32 | 33 | // set auto page breaks 34 | $this->setAutoPageBreak(true, 1/2); 35 | 36 | // set image scale factor 37 | $this->setImageScale(0); 38 | 39 | // set default font subsetting mode 40 | $this->setFontSpacing(0); 41 | $this->setFontStretching(100); 42 | $this->setFontSubsetting(true); 43 | 44 | // Cells 45 | $this->setCellMargins(0, 0, 0, 0); 46 | $this->setCellPaddings(0, 0, 0, 0); 47 | 48 | // Set font 49 | $this->setFont('freesans', '', 14, '', true); 50 | 51 | // Set viewer preferences 52 | $arg = array( 53 | 'HideToolbar' => true, 54 | 'HideMenubar' => true, 55 | 'HideWindowUI' => true, 56 | 'FitWindow' => true, 57 | 'CenterWindow' => true, 58 | 'DisplayDocTitle' => true, 59 | 'NonFullScreenPageMode' => 'UseNone', // UseNone, UseOutlines, UseThumbs, UseOC 60 | 'ViewArea' => 'CropBox', // CropBox, BleedBox, TrimBox, ArtBox 61 | 'ViewClip' => 'CropBox', // CropBox, BleedBox, TrimBox, ArtBox 62 | 'PrintArea' => 'CropBox', // CropBox, BleedBox, TrimBox, ArtBox 63 | 'PrintClip' => 'CropBox', // CropBox, BleedBox, TrimBox, ArtBox 64 | 'PrintScaling' => 'None', // None, AppDefault 65 | 'Duplex' => 'Simplex', // Simplex, DuplexFlipShortEdge, DuplexFlipLongEdge 66 | //'PickTrayByPDFSize' => true, 67 | //'PrintPageRange' => array(1,1,2,3), 68 | //'NumCopies' => 2 69 | ); 70 | $this->setViewerPreferences($arg); 71 | $this->SetDisplayMode('fullwidth'); 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /lib/Product.php: -------------------------------------------------------------------------------- 1 | _public = $cfg['public']; 19 | $this->_secret = $cfg['secret']; 20 | } 21 | 22 | function getAuthToken() 23 | { 24 | $req = _curl_init('https://api-g.weedmaps.com/auth/token'); 25 | 26 | $req_body = json_encode([ 27 | 'client_id' => $this->_public, 28 | 'client_secret' => $this->_secret, 29 | 'grant_type' => 'client_credentials', 30 | 'scope' => 'taxonomy:read brands:read products:read menu_items menus:write', 31 | ]); 32 | curl_setopt($req, CURLOPT_POST, true); 33 | curl_setopt($req, CURLOPT_POSTFIELDS, $req_body); 34 | 35 | $req_head = []; 36 | $req_head[] = 'accept: application/json'; 37 | $req_head[] = 'content-type: application/json'; 38 | // $req_head[] = 39 | 40 | curl_setopt($req, CURLOPT_HTTPHEADER, $req_head); 41 | 42 | $res = curl_exec($req); 43 | $res = json_decode($res, true); 44 | 45 | if (!empty($res['created_at']) && !empty($res['expires_in'])) { 46 | $res['expires_at'] = $res['created_at'] + $res['expires_in']; 47 | $res['expires_at_iso'] = date(\DateTime::ISO8601, $res['expires_at']); 48 | } 49 | 50 | $this->setAuthToken($res['access_token']); 51 | 52 | return $res; 53 | 54 | } 55 | 56 | function setAuthToken($t) 57 | { 58 | $this->_auth_token = $t; 59 | } 60 | 61 | /** 62 | * 63 | */ 64 | function get($path) 65 | { 66 | $url = sprintf('https://api-g.weedmaps.com%s', $path); 67 | 68 | $req = _curl_init($url); 69 | 70 | $req_head = []; 71 | $req_head[] = 'accept: application/json'; 72 | $req_head[] = sprintf('authorization: Bearer %s', $this->_auth_token); 73 | curl_setopt($req, CURLOPT_HTTPHEADER, $req_head); 74 | 75 | $res = curl_exec($req); 76 | $res = json_decode($res, true); 77 | 78 | return $res; 79 | 80 | } 81 | 82 | /** 83 | * 84 | */ 85 | function post_form($path, $body) 86 | { 87 | $url = sprintf('https://api-g.weedmaps.com%s', $path); 88 | $req = _curl_init($url); 89 | curl_setopt($req, CURLOPT_POST, true); 90 | curl_setopt($req, CURLOPT_POSTFIELDS, $body); 91 | 92 | $req_head = []; 93 | $req_head[] = 'accept: application/json'; 94 | $req_head[] = sprintf('authorization: Bearer %s', $this->_auth_token); 95 | $req_head[] = 'content-type: application/x-www-form-urlencoded'; 96 | curl_setopt($req, CURLOPT_HTTPHEADER, $req_head); 97 | 98 | $res = curl_exec($req); 99 | $res = json_decode($res, true); 100 | 101 | return $res; 102 | 103 | } 104 | 105 | /** 106 | * 107 | */ 108 | function post_json($path, $body) 109 | { 110 | $url = sprintf('https://api-g.weedmaps.com%s', $path); 111 | $req = _curl_init($url); 112 | curl_setopt($req, CURLOPT_POST, true); 113 | curl_setopt($req, CURLOPT_POSTFIELDS, $body); 114 | 115 | $req_head = []; 116 | $req_head[] = 'accept: application/json'; 117 | $req_head[] = sprintf('authorization: Bearer %s', $this->_auth_token); 118 | $req_head[] = 'content-type: application/json'; 119 | curl_setopt($req, CURLOPT_HTTPHEADER, $req_head); 120 | 121 | $res = curl_exec($req); 122 | $res = json_decode($res, true); 123 | 124 | return $res; 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/Traits/OpenAuthBox.php: -------------------------------------------------------------------------------- 1 | pk, $cpk) !== 0) { 24 | throw new \Exception('Authentication Box Invalid Service Key [PCB-032]', 403); 25 | } 26 | 27 | // Time Check 28 | $dt0 = new \DateTime(); 29 | $dt1 = \DateTime::createFromFormat('U', $act->ts); 30 | $age = $dt0->diff($dt1, true); 31 | if (($age->d != 0) || ($age->h != 0) || ($age->i > 5)) { 32 | throw new \Exception('Authentication Box Expired [PCB-040]', 400); 33 | } 34 | 35 | if (empty($act->contact)) { 36 | throw new \Exception('Authentication Box Data Corrupted [PCB-103]', 403); 37 | } 38 | 39 | if (empty($act->company)) { 40 | throw new \Exception('Authentication Box Data Corrupted [PCB-110]', 403); 41 | } 42 | 43 | return $act; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Install Helper 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-only 6 | # 7 | 8 | set -o errexit 9 | set -o errtrace 10 | set -o nounset 11 | set -o pipefail 12 | 13 | APP_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 14 | 15 | cd "$APP_ROOT" 16 | 17 | composer install --no-ansi --no-progress --classmap-authoritative 18 | 19 | npm install --no-audit --no-fund 20 | 21 | php <&1; 57 | 58 | # 59 | # 60 | php <client = $this->getGuzzleClient([ 16 | 'base_uri' => OPENTHC_TEST_ORIGIN 17 | ]); 18 | } 19 | 20 | function makeBearerToken() 21 | { 22 | // Client Public Key 23 | $cpk = \OpenTHC\Config::get('openthc/pos/public'); 24 | // Client Secret Key 25 | $csk = \OpenTHC\Config::get('openthc/pos/secret'); 26 | // Server Public Key (same as Client when requesting to self) 27 | $spk = \OpenTHC\Config::get('openthc/pos/public'); 28 | 29 | // Crypto Box of Data 30 | $box = json_encode([ 31 | 'pk' => $cpk, 32 | 'ts' => time(), 33 | 'contact' => '', 34 | 'company' => '', 35 | 'license' => '', 36 | ]); 37 | $box = \OpenTHC\Sodium::encrypt($box, $csk, $spk); 38 | $box = \OpenTHC\Sodium::b64encode($box); 39 | 40 | return sprintf('Bearer v2024/%s/%s', $cpk, $box); 41 | } 42 | 43 | /** 44 | HTTP Utility 45 | */ 46 | function get($url) 47 | { 48 | $res = $this->ghc->get($url); 49 | $ret = $this->assertValidResponse($res, 200); 50 | return $ret; 51 | } 52 | 53 | 54 | /** 55 | HTTP Utility 56 | */ 57 | function post($url, $arg) 58 | { 59 | $res = $this->ghc->post($url, array('json' => $arg)); 60 | return $res; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /test/Core/Config_Test.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_file($f), 'No Config File'); 14 | } 15 | 16 | function test_service_config() 17 | { 18 | $cfg = \OpenTHC\Config::get(''); 19 | // var_dump($cfg); 20 | 21 | $this->assertIsArray($cfg); 22 | 23 | $key_list = [ 24 | 'database/auth/hostname', 25 | 'database/auth/username', 26 | 'database/auth/password', 27 | 'database/auth/database', 28 | 29 | 'database/main/hostname', 30 | 'database/main/username', 31 | 'database/main/password', 32 | 'database/main/database', 33 | 34 | 'redis/hostname', 35 | 36 | 'statsd/hostname', 37 | 38 | // 'metabase/hostname', 39 | // 'metabase/username', 40 | // 'metabase/password', 41 | // 'metabase/embedkey', 42 | 43 | 'openthc/dir/origin', 44 | 'openthc/dir/public', 45 | 46 | 'openthc/pos/origin', 47 | 'openthc/pos/public', 48 | 'openthc/pos/secret', 49 | 'openthc/pos/client-id', 50 | 'openthc/pos/client-sk', 51 | 52 | 'openthc/b2b/origin', // Menu New Name 53 | 54 | // 'openthc/ops/origin', 55 | // 'openthc/ops/public', 56 | // 'openthc/ops/secret', 57 | 58 | 'openthc/sso/origin', 59 | 'openthc/sso/public', 60 | 61 | ]; 62 | 63 | foreach ($key_list as $k) { 64 | $v = \OpenTHC\Config::get($k); 65 | $this->assertNotEmpty($v, sprintf('Config %s is missing', $k)); 66 | } 67 | 68 | $key_list = [ 69 | 'openthc/app/secret', 70 | 'openthc/b2b/secret', 71 | 'openthc/cre/secret', 72 | 'openthc/dir/secret', 73 | 'openthc/lab/secret', 74 | 'openthc/pipe/secret', 75 | 'openthc/sso/secret', 76 | ]; 77 | 78 | foreach ($key_list as $k) { 79 | $v = \OpenTHC\Config::get($k); 80 | $this->assertEmpty($v, sprintf('Config %s should be empty/unset', $k)); 81 | } 82 | 83 | } 84 | 85 | /** 86 | * 87 | */ 88 | function test_api() 89 | { 90 | $cfg = \OpenTHC\Config::get('openthc/pos'); 91 | $this->assertIsArray($cfg); 92 | 93 | $this->assertArrayHasKey('client-id', $cfg); 94 | $this->assertNotEmpty($cfg['client-id']); 95 | 96 | $this->assertArrayHasKey('client-pk', $cfg); 97 | $this->assertNotEmpty($cfg['client-pk']); 98 | 99 | $this->assertArrayHasKey('client-sk', $cfg); 100 | $this->assertNotEmpty($cfg['client-sk']); 101 | 102 | } 103 | 104 | /** 105 | * 106 | */ 107 | function test_redis() 108 | { 109 | // @todo Check for Redis Config and try to connect 110 | $h = \OpenTHC\Config::get('redis/hostname'); 111 | $this->assertNotEmpty($h); 112 | 113 | $k = _random_hash(); 114 | $r = new \Redis(); 115 | $r->connect($h); 116 | $r->set($k, 'TEST'); 117 | 118 | $x = $r->get($k); 119 | $this->assertEquals('TEST', $x); 120 | 121 | } 122 | 123 | function test_statsd() 124 | { 125 | // @todo Check for Config & functions? 126 | $h = \OpenTHC\Config::get('statsd/hostname'); 127 | $this->assertNotEmpty($h); 128 | 129 | $this->assertTrue(function_exists('_stat_counter')); 130 | $this->assertTrue(function_exists('_stat_gauge')); 131 | $this->assertTrue(function_exists('_stat_timer')); 132 | 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/Core/Core_Test.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_dir($dir)); 18 | 19 | // Var Path 20 | $var = sprintf('%s/var', APP_ROOT); 21 | $this->assertTrue(is_dir($var)); 22 | 23 | $var_stat = stat($var); 24 | 25 | $o = posix_getpwuid($var_stat[4]); 26 | $this->assertIsArray($o); 27 | $this->assertEquals('openthc', $o['name']); 28 | 29 | $g = posix_getgrgid($var_stat[5]); 30 | $this->assertIsArray($g); 31 | $this->assertEquals('www-data', $g['name']); 32 | 33 | $m = ($var_stat[2] & 0x0fff); 34 | $this->assertEquals($m, 0775); // Perms in OCTAL 35 | 36 | // Webroot Output Path 37 | $dir = sprintf('%s/webroot/output', APP_ROOT); 38 | $dir_stat = stat($dir); 39 | 40 | $o = posix_getpwuid($dir_stat[4]); 41 | $this->assertIsArray($o); 42 | $this->assertEquals('openthc', $o['name']); 43 | 44 | $g = posix_getgrgid($dir_stat[5]); 45 | $this->assertIsArray($g); 46 | $this->assertEquals('www-data', $g['name']); 47 | 48 | $m = ($dir_stat[2] & 0x0fff); 49 | $this->assertEquals($m, 0775); // Perms in OCTAL 50 | 51 | // @todo Check all the other directories/files to be owned by 'openthc' 52 | 53 | // $this->assertIsTrue(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Core/System_Test.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_file($f), '/usr/bin/convert not found'); 14 | $this->assertTrue(is_executable($f), '/usr/bin/convert not executable'); 15 | } 16 | 17 | function test_convert_policy() 18 | { 19 | // Read the Convert Policy XML File 20 | // Make sure that PS and PDF *ARE* Allowed 21 | $xml_file = '/etc/ImageMagick-6/policy.xml'; 22 | $this->assertTrue(is_file($xml_file), 'No File'); 23 | $xml_data = simplexml_load_file($xml_file); 24 | // Do the tright thing here 25 | } 26 | 27 | function test_exiftool() 28 | { 29 | $f = '/usr/bin/exiftool'; 30 | $this->assertTrue(is_file($f)); 31 | $this->assertTrue(is_executable($f)); 32 | } 33 | 34 | 35 | function test_gs() 36 | { 37 | $f = '/usr/bin/gs'; 38 | $this->assertTrue(is_file($f), '/usr/bin/gs does not exist'); 39 | $this->assertTrue(is_executable($f), '/usr/bin/gs is not executable'); 40 | } 41 | 42 | function test_pdfunite() 43 | { 44 | $f = '/usr/bin/pdfunite'; 45 | $this->assertTrue(is_file($f)); 46 | $this->assertTrue(is_executable($f)); 47 | } 48 | 49 | // function test_puppeteer() 50 | // { 51 | // $f = '/usr/bin/node'; 52 | // $this->assertTrue(is_file($f), '/usr/bin/node does not exist'); 53 | // $this->assertTrue(is_executable($f), '/usr/bin/node is not Executable'); 54 | 55 | // $f = sprintf('%s/node_modules/puppeteer/index.js', APP_ROOT); 56 | // $this->assertTrue(is_file($f), 'puppeteer/index.js does not exist'); 57 | // } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /test/Unit/API_Test.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'Authorization' => $this->makeBearerToken(), 17 | 'openthc-contact-id' => '', 18 | 'openthc-company-id' => '', 19 | 'openthc-license-id' => '', 20 | ], 21 | 'json' => [ 22 | 'id' => '', 23 | 'created_at' => '', 24 | ] 25 | ]; 26 | $res = $this->client->post('/api/v2018/b2c', $arg); 27 | $res = $this->assertValidResponse($res); 28 | 29 | var_dump($res); 30 | 31 | return $b2c_sale; 32 | } 33 | 34 | /** 35 | * @depends test_b2c_sale_create 36 | */ 37 | function test_b2c_sale_item_create($b2c_sale) 38 | { 39 | $url = sprintf('/api/v2018/b2c/%s/item', $b2c_sale->id); 40 | $arg = [ 41 | 'headers' => [ 42 | 'Authorization' => $this->makeBearerToken(), 43 | ], 44 | 'json' => [ 45 | 'inventory' => [ 'id' => '' ], 46 | 'unit_count' => 1, 47 | 'unit_price' => 2, 48 | ] 49 | ]; 50 | 51 | $res = $this->client->post($url, $arg); 52 | $res = $this->assertValidResponse($res); 53 | 54 | var_dump($res); 55 | 56 | return $b2c_sale; 57 | } 58 | 59 | /** 60 | * @depends test_b2c_sale_item_create 61 | */ 62 | function test_b2c_sale_detail($b2c_sale) 63 | { 64 | $url = sprintf('/api/v2018/b2c/%s', $b2c_sale->id); 65 | $arg = [ 66 | 'headers' => [ 67 | 'Authorization' => $this->makeBearerToken(), 68 | ], 69 | ]; 70 | $res = $this->client->get($url); 71 | $res = $this->assertValidResponse($res); 72 | 73 | var_dump($res); 74 | 75 | return $b2c_sale; 76 | } 77 | 78 | /** 79 | * @depends test_b2c_sale_detail 80 | */ 81 | function test_b2c_sale_verify($b2c_sale) 82 | { 83 | $url = sprintf('/api/v2018/b2c/%s/verify', $b2c_sale->id); 84 | $arg = [ 85 | 'headers' => [ 86 | 'Authorization' => $this->makeBearerToken(), 87 | ], 88 | ]; 89 | $res = $this->client->post($url); 90 | $res = $this->assertValidResponse($res); 91 | 92 | return $b2c_sale; 93 | } 94 | 95 | /** 96 | * @depends test_b2c_sale_verify 97 | */ 98 | function test_b2c_sale_commit($b2c_sale) 99 | { 100 | $url = sprintf('/api/v2018/b2c/%s/commit', $b2c_sale->id); 101 | $arg = [ 102 | 'headers' => [ 103 | 'Authorization' => $this->makeBearerToken(), 104 | ], 105 | 'json' => [ 106 | 'key' => 'val', 107 | ] 108 | ]; 109 | $res = $this->client->post($url); 110 | $res = $this->assertValidResponse($res); 111 | 112 | return $b2c_sale; 113 | } 114 | 115 | /** 116 | * @depends test_b2c_sale_commit 117 | */ 118 | function test_b2c_sale_commit_fail($b2c_sale) 119 | { 120 | $url = sprintf('/api/v2018/b2c/%s/commit', $b2c_sale->id); 121 | $arg = [ 122 | 'headers' => [ 123 | 'Authorization' => $this->makeBearerToken(), 124 | ], 125 | 'json' => [ 126 | 'key' => 'val', 127 | ] 128 | ]; 129 | $res = $this->client->post($url); 130 | $res = $this->assertValidResponse($res, 409); 131 | 132 | return $b2c_sale; 133 | 134 | } 135 | 136 | } 137 | 138 | # -X POST \ 139 | # --header 'content-type: application/json' \ 140 | # --data '{ "Email": "npc@nationalpaymentcard.com", "MerchantID": "TEST", "PIN": "1234", "ReturnPseudoCardNumber": true }' 141 | 142 | #Response:- 143 | # { 144 | #"data": null, 145 | #"meta": { 146 | #"detail": "API query '/api/v2017/b2c/commit' was not understood [CA7#024]" 147 | #} 148 | #} 149 | 150 | # Create B2C Sale Record 151 | # curl 'https://app.djb.openthc.dev/api/v2017/b2c' \ 152 | # -X 'POST' \ 153 | # --header 'authorization: Bearer uEu72zR6brePjYdt6J2pIBzf5SfcJ9K4ccz0Q1AELI65EAW88QbBuFOXDUUEA_g7' \ 154 | # --header 'content-type: application/json' \ 155 | # --data '{ "license": { "id": "$OPENTHC_LICENSE" } }' 156 | 157 | # # Add Item to B2C Sale Record 158 | # curl 'https://app.djb.openthc.dev/api/v2017/b2c/item' \ 159 | # -X 'POST' \ 160 | # --header 'authorization: Bearer uEu72zR6brePjYdt6J2pIBzf5SfcJ9K4ccz0Q1AELI65EAW88QbBuFOXDUUEA_g7' \ 161 | # --header 'content-type: application/json' \ 162 | # --data '{ "license": { "id": "$OPENTHC_LICENSE" } , "b2c": { "id": $B2C_SALE_ID }, "inventory": { "id": "01FCH7F3AADC2BENAM4V7JBT8Q" }, "unit_count": 5, "unit_price": 5 }' 163 | 164 | # # Commit 165 | # curl 'https://app.djb.openthc.dev/api/v2017/b2c/commit' \ 166 | # -X 'POST' \ 167 | # --header 'authorization: Bearer uEu72zR6brePjYdt6J2pIBzf5SfcJ9K4ccz0Q1AELI65EAW88QbBuFOXDUUEA_g7' \ 168 | # --header 'content-type: application/json' \ 169 | # --data '{ "license": { "id": "$OPENTHC_LICENSE" } , "b2c": { "id": $B2C_SALE_ID } }' 170 | -------------------------------------------------------------------------------- /test/Webhook/Weedmaps_Test.php: -------------------------------------------------------------------------------- 1 | client->post('/webhook/weedmaps/order', [ 'json' => [ 13 | 'status' => 'PENDING', 14 | ]]); 15 | $this->assertValidResponse($res); 16 | } 17 | 18 | function test_pending() 19 | { 20 | $res = $this->client->post('/webhook/weedmaps/order', [ 'json' => [ 21 | 'status' => 'PENDING', 22 | ]]); 23 | $this->assertValidResponse($res); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /test/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | . 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/test.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [options] 23 | test phpunit 24 | test phpstan 25 | test phplint 26 | 27 | Options: 28 | --phpunit-filter= Some Filter for PHPUnit 29 | --phpunit-filter= Some Filter for PHPUnit 30 | DOC; 31 | 32 | $res = \Docopt::handle($doc, [ 33 | 'exit' => false, 34 | 'optionsFirst' => false, 35 | ]); 36 | $cli_args = $res->args; 37 | // if (empty($cli_args)) { 38 | // echo $res->output; 39 | // echo "\n"; 40 | // exit(1); 41 | // } 42 | // var_dump($cli_args); 43 | 44 | 45 | // Test Config 46 | $cfg = []; 47 | $cfg['base'] = APP_ROOT; 48 | $cfg['site'] = 'pos'; 49 | 50 | $test_helper = new \OpenTHC\Test\Helper($cfg); 51 | $cfg['output'] = $test_helper->output_path; 52 | 53 | 54 | // PHPLint 55 | if ($cli_args['phplint']) { 56 | $tc = new \OpenTHC\Test\Facade\PHPLint($cfg); 57 | $res = $tc->execute(); 58 | var_dump($res); 59 | } 60 | 61 | 62 | // PHPStan 63 | if ($cli_args['phpstan']) { 64 | $tc = new \OpenTHC\Test\Facade\PHPStan($cfg); 65 | $res = $tc->execute(); 66 | var_dump($res); 67 | } 68 | 69 | 70 | // Psalm/Psalter? 71 | 72 | 73 | // PHPUnit 74 | $cfg_file_list = []; 75 | $cfg_file_list[] = sprintf('%s/phpunit.xml', __DIR__); 76 | $cfg_file_list[] = sprintf('%s/phpunit.xml.dist', __DIR__); 77 | foreach ($cfg_file_list as $f) { 78 | if (is_file($f)) { 79 | $cfg['--configuration'] = $f; 80 | break; 81 | } 82 | } 83 | // Filter? 84 | if ( ! empty($cli_args['--filter'])) { 85 | $cfg['--filter'] = $cli_args['--filter']; 86 | } 87 | $tc = new \OpenTHC\Test\Facade\PHPUnit($cfg); 88 | $res = $tc->execute(); 89 | var_dump($res); 90 | 91 | 92 | // Output 93 | $res = $test_helper->index_create($res['data']); 94 | echo "TEST COMPLETE\n $res\n"; 95 | -------------------------------------------------------------------------------- /view/_block/body-head.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | -------------------------------------------------------------------------------- /view/_block/inventory-list.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 |
Inventory IDProductPackageQTY$/ea
27 | -------------------------------------------------------------------------------- /view/_block/modal.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | -------------------------------------------------------------------------------- /view/_block/modal/pos/card-swipe.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 27 | 28 | 38 | 39 | echo $this->block('modal.php', [ 40 | 'modal_id' => 'pos-modal-checkout-card-swipe', 41 | 'modal_title' => 'Scan ID', 42 | 'body' => $body, 43 | 'foot' => $foot, 44 | ]); 45 | -------------------------------------------------------------------------------- /view/_block/modal/pos/cart-options.php: -------------------------------------------------------------------------------- 1 | 10 |
11 |
Transaction Date:
12 |

Enter the Date to Register this Transaction

13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 | HTML; 23 | 24 | $foot = << Apply 30 | 31 | HTML; 32 | 33 | echo $this->block('modal.php', [ 34 | 'modal_id' => 'pos-modal-cart-option', 35 | 'modal_title' => 'Cart :: Options', 36 | 'body' => $body, 37 | 'foot' => $foot, 38 | ]); 39 | -------------------------------------------------------------------------------- /view/_block/modal/pos/customer-info.php: -------------------------------------------------------------------------------- 1 | 13 |
14 |
15 | 22 |
23 | 40 | Save 41 | 42 | HTML; 43 | 44 | echo $this->block('modal.php', [ 45 | 'modal_id' => 'pos-modal-customer-info', 46 | 'modal_title' => 'Cart :: Customer Information', 47 | 'body' => $body, 48 | 'foot' => $foot, 49 | ]); 50 | -------------------------------------------------------------------------------- /view/_block/modal/pos/discount.php: -------------------------------------------------------------------------------- 1 | 10 |
Fixed Discount
11 |
12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 |
Percent Discount
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 | 31 | 32 |
33 |
Balance:
34 |
35 |

36 |
37 |
38 |
39 |
Applied Discount
40 |
41 |

-

42 |
43 |
44 |
45 |
New Balance
46 |
47 |

-

48 |
49 |
50 | 51 | 61 | HTML; 62 | 63 | 64 | $foot = << 69 | Apply 70 | 71 | HTML; 72 | 73 | echo $this->block('modal.php', [ 74 | 'modal_id' => 'pos-modal-discount', 75 | 'modal_title' => 'Checkout :: Discount', 76 | 'body' => $body, 77 | 'foot' => $foot, 78 | ]); 79 | -------------------------------------------------------------------------------- /view/_block/modal/pos/hold.php: -------------------------------------------------------------------------------- 1 | 11 |
12 | 13 | 14 |
15 | 16 | HTML; 17 | 18 | 19 | $foot = << 27 | Save 28 | 29 | HTML; 30 | 31 | 32 | echo $this->block('modal.php', [ 33 | 'modal_id' => 'pos-modal-sale-hold', 34 | 'modal_title' => 'Checkout :: Create Hold', 35 | 'body' => $body, 36 | 'foot' => $foot, 37 | ]); 38 | 39 | ?> 40 | 41 | 53 | -------------------------------------------------------------------------------- /view/_block/modal/pos/keypad.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openthc/pos/d54b19901e8d4e075a7a8802eaca36e1bf9ef7f6/view/_block/modal/pos/keypad.php -------------------------------------------------------------------------------- /view/_block/modal/pos/loyalty.php: -------------------------------------------------------------------------------- 1 | 12 |
13 |
Code / ID
14 |

Their Loyalty ID or scanned ID card

15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 | HTML; 29 | 30 | $foot = << Apply 32 | HTML; 33 | 34 | 35 | echo $this->block('modal.php', [ 36 | 'modal_id' => 'pos-modal-loyalty', 37 | 'modal_title' => 'Checkout :: Loyalty', 38 | 'body' => $body, 39 | 'foot' => $foot, 40 | ]); 41 | -------------------------------------------------------------------------------- /view/_block/modal/pos/payment-card.php: -------------------------------------------------------------------------------- 1 | 10 |
11 |
12 |

Sub-Total: $-.--

13 |
14 |
15 |

Taxes: $-.--

16 |
17 |
18 |

Total Due: $

19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 | Paid: 41 |
42 | 43 |
44 |
45 |
46 | Due: 47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 |

Change: $0.00

55 |
56 |
57 | 58 | HTML; 59 | 60 | 61 | $foot = << Undo 63 | 64 | HTML; 65 | 66 | 67 | echo $this->block('modal.php', [ 68 | 'modal_id' => 'pos-modal-payment', 69 | 'modal_title' => 'Checkout :: Payment :: Card', 70 | 'body' => $body, 71 | 'foot' => $foot, 72 | ]); 73 | -------------------------------------------------------------------------------- /view/_block/modal/pos/payment-cash.php: -------------------------------------------------------------------------------- 1 | 10 |
11 |

Sub-Total: $-.--

12 |
13 |
14 |

Taxes: $-.--

15 |
16 |
17 |

Total Due: $

18 |
19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 |
61 |
62 |
Paid:
63 |
0.00
64 |
65 |
66 |
67 |
68 |
69 |
70 |
Due:
71 |
0.00
72 |
73 |
74 |
75 |
76 | HTML; 77 | 78 | 79 | $foot = << Undo 81 | 84 | HTML; 85 | 86 | 87 | echo $this->block('modal.php', [ 88 | 'modal_id' => 'pos-modal-payment', 89 | 'modal_title' => 'Checkout :: Payment :: Cash', 90 | 'body' => $body, 91 | 'foot' => $foot, 92 | ]); 93 | -------------------------------------------------------------------------------- /view/_block/modal/pos/scan-id.php: -------------------------------------------------------------------------------- 1 | 10 |
11 | HTML; 12 | 13 | $foot = << Complete 15 | HTML; 16 | 17 | echo $this->block('modal.php', [ 18 | 'modal_id' => 'pos-modal-scan-id', 19 | 'modal_title' => 'Scan ID', 20 | 'body' => $body, 21 | 'foot' => $foot, 22 | ]); 23 | -------------------------------------------------------------------------------- /view/_block/modal/pos/transaction-limit.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 35 | 54 | -------------------------------------------------------------------------------- /view/_block/session-flash.php: -------------------------------------------------------------------------------- 1 |
%s
', $x); 16 | } 17 | -------------------------------------------------------------------------------- /view/_layout/html-pos.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <?= __h(strip_tags($this->data['Page']['title'])) ?> 24 | 25 | 26 | 50 | 51 | block('session-flash.php') ?> 52 | 53 | body ?> 54 | 55 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 95 | foot_script ?> 96 | 97 | 98 | -------------------------------------------------------------------------------- /view/_layout/html.php: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <?= __h(strip_tags($this->data['Page']['title'])) ?> 27 | 28 | 29 | block('body-head.php') ?> 30 | block('session-flash.php') ?> 31 |
32 | body ?> 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 55 | foot_script ?> 56 | 57 | 58 | -------------------------------------------------------------------------------- /view/_layout/shop-html.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <?= __h(strip_tags($this->data['Page']['title'])) ?> 25 | 26 | 27 | 28 | block('session-flash.php') ?> 29 |
30 | body ?> 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | foot_script ?> 39 | 40 | 41 | -------------------------------------------------------------------------------- /view/contact/update.php: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 58 | -------------------------------------------------------------------------------- /view/crm/contact.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |

Customer Contact List

7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | '; 23 | printf('', $c['id'], __h($c['fullname']) ); 24 | printf('', $c['email'] ); 25 | printf('', $c['phone'] ); 26 | echo ''; 27 | } 28 | ?> 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
NameEmailPhoneTags
%s%s%s
40 |
41 | 42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /view/crm/main.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 |
8 | Contacts', "View your Contacts, once Contacts are added to this program, they will appear here.", 'Manage') ?> 9 |
10 |
11 | Messaging', "Send message to one or more Contacts, either via email or text/sms message.", 'Messaging') ?> 12 |
13 |
14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /view/crm/message-compose-email.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |

7 |

8 | To send proper emails, use an external HTML & TEXT formatting tool and paste that contents here. 9 | 10 |

11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /view/crm/message-compose-sms.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |

7 | 8 |

Input the text base messages, and attach messages.

9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /view/crm/message.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |

Messaging

7 | 8 |
9 |
10 | Send Texts') ?> 11 |
12 |
13 | Send Emails') ?> 14 |
15 |
16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /view/dashboard.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |

::

11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 | Retail Sales 19 |
20 |
21 | 22 | View Details 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 | Delivery 35 |
36 |

Delivery Management

37 |
38 | 39 | View Details 40 | 41 | 42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 | On-Line 52 |
53 |

Online Sales

54 |
55 | 56 | View Details 57 | 58 | 59 | 60 | 61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /view/done.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 |
13 |

14 |
15 | 16 | %s
', h($data['fail'])); 19 | } 20 | 21 | if ($data['warn']) { 22 | printf('
%s
', h($data['warn'])); 23 | } 24 | 25 | if ($data['info']) { 26 | printf('
%s
', h($data['info'])); 27 | } 28 | 29 | echo $data['body'] 30 | 31 | ?> 32 | 33 |
34 | %s
', $data['foot']); 37 | } 38 | ?> 39 | 40 | 41 | -------------------------------------------------------------------------------- /view/intent/delivery-auth.php: -------------------------------------------------------------------------------- 1 | data['Page']['title'] = 'Delivery Staff Authentication'; 7 | 8 | if (empty($_COOKIE['pos-contact'])) { 9 | // Prompt for Username 10 | } 11 | 12 | require_once(sprintf('%s/view/pos/open.php', APP_ROOT)); 13 | -------------------------------------------------------------------------------- /view/intent/main.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openthc/pos/d54b19901e8d4e075a7a8802eaca36e1bf9ef7f6/view/intent/main.php -------------------------------------------------------------------------------- /view/intent/vendor-view.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /view/pos/checkout/done.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/html-pos.php', APP_ROOT); 7 | 8 | ?> 9 | 10 | 13 | -------------------------------------------------------------------------------- /view/pos/contact-select.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/html-pos.php', APP_ROOT); 9 | 10 | $head = sprintf('

%s

', _('Check In:')); 11 | 12 | $govt_id_html = __h($_SESSION['Cart']['contact-search']); 13 | 14 | $body = <<Scan the client's identification card or input their identification details. 17 | 18 |
19 | 20 |
Transaction:
21 | 25 | 26 |
27 | 28 |
29 | 30 |
Identification:
31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 55 |
56 | 57 | HTML; 58 | 59 | $foot = []; 60 | $foot[] = '
'; 61 | $foot[] = '
'; 62 | 63 | $foot[] = ''; 64 | 65 | if ($_SESSION['Cart']['contact-push']) { 66 | unset($_SESSION['Cart']['contact-push']); 67 | $foot[] = ''; 68 | } 69 | 70 | $foot[] = ''; 71 | 72 | $foot[] = '
'; 73 | $foot[] = '
'; 74 | $foot[] =''; 75 | $foot[] = '
'; 76 | $foot[] = '
'; 77 | $foot = implode(' ', $foot); 78 | 79 | ?> 80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 | 232 | -------------------------------------------------------------------------------- /view/pos/contact-verify.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/html-pos.php', APP_ROOT); 9 | 10 | $head = sprintf('

%s

', _('Client Contact Verify:')); 11 | 12 | ob_start(); 13 | ?> 14 | 15 |
16 |
Customer Type:
17 | 25 |
26 | 27 | 28 |
29 |
Primary ID:
30 | 37 |
38 | 39 | 40 |
41 |
Secondary ID:
42 | 49 |
50 | 51 | 52 |
53 |
Full Name:
54 | 61 |
62 | 63 | 64 |
65 |
DOB:
66 | 73 |
74 | 75 | 76 | 87 | 88 | '; 102 | $foot[] = '
'; 103 | $foot[] = ''; 104 | // $foot[] = ''; 105 | // $foot[] = ''; 106 | $foot[] = '
'; 107 | // $foot[] = '
'; 108 | // $foot[] =''; 109 | // $foot[] = '
'; 110 | $foot[] = ''; 111 | $foot = implode(' ', $foot); 112 | 113 | ?> 114 | 115 |
116 |
117 | 118 |
119 |
120 | 121 | 127 |
128 |
129 | 136 |
137 | 3 |
4 |

POS Delivery

5 |
6 |
'delivery-auth', 11 | 'c' => $_SESSION['Company']['id'], 12 | 'l' => $_SESSION['License']['id'] 13 | ]) 14 | ); 15 | // printf('' 16 | // , $delivery_auth_link 17 | // ); 18 | 19 | $link = sprintf('/pos/delivery/ajax?%s' 20 | , http_build_query([ 21 | 'a' => 'delivery-auth', 22 | 'c' => $_SESSION['Company']['id'], 23 | 'l' => $_SESSION['License']['id'] 24 | ]) 25 | ); 26 | printf('' 27 | , $link 28 | ); 29 | 30 | ?>
31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 |

Active Couriers

39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | '; 53 | printf('', $c['name']); 54 | printf('', $c['stat'], $ping); 55 | printf('', $c['location']); 56 | echo ''; 57 | } 58 | ?> 59 | 60 | 61 | 62 | 71 | 74 | 75 | 76 |
CourierActiveLocation
%s%s / %s%s
63 | 70 | 72 | 73 |
77 |
78 | 79 |
80 | 81 |

Orders

82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | $v) { 101 | if (preg_match('/^item\-(\w+)\-unit\-count$/', $k, $m)) { 102 | $b2c_item['item_list'][] = [ 103 | 'id' => $m[1], 104 | ]; 105 | } 106 | } 107 | 108 | echo ''; 109 | printf('', $rec['id'], $rec['id']); 110 | printf('', $rec['contact_name']); 111 | printf('', count($b2c_item['item_list'])); 112 | echo ''; 113 | } 114 | ?> 115 | 116 |
OrderCourierCart
%s%s%d Items
117 |
118 |
119 |
120 |
121 |

Map Loading...

122 |
123 |
124 |
125 | 126 | 127 | 128 | 182 | -------------------------------------------------------------------------------- /view/pos/main.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | PIN Auth Screen 6 | ID Scanner 7 | 8 | Check In Kiosk 9 | 10 | Register 11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /view/pos/online.php: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 |

Online Orders

11 |

These orders can come from external systems, if you have them configured or from your own website

12 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | $v) { 35 | $item_info[] = sprintf('%s %s', $v['product']['name'], $v['variety']['name']); 36 | } 37 | $rec['item_info'] = implode(', ', $item_info); 38 | 39 | echo ''; 40 | printf('', $rec['id']); 41 | printf('', $rec['contact_name']); 42 | // printf('', $rec['meta']); 43 | printf('', $rec['item_info']); 44 | echo ''; 45 | 46 | } 47 | ?> 48 | 49 |
OrderContactDetails
%s%s
%s
%s
50 |
51 | 52 |
53 | -------------------------------------------------------------------------------- /view/pos/open.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 | 119 | -------------------------------------------------------------------------------- /view/pos/terminal/shut.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

Terminal Session Closed

12 | 13 |
14 | Terminal Home 15 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /view/report/recent.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | '; 33 | $action_ux.= ''; 34 | $action_ux.= ''; 35 | $action_ux = sprintf($action_ux, $b2c_transaction['id']); 36 | break; 37 | default: 38 | $status = 'Closed'; 39 | $action_ux = ''; 40 | } 41 | ?> 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 |
SaleDateContactTotalStatus
56 |
57 | 58 | 76 | 77 | -------------------------------------------------------------------------------- /view/shop/cart.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/shop-html.php', APP_ROOT); 7 | 8 | ?> 9 | 10 |
11 |

12 |
13 | Storefront 14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 | 25 |
26 | 29 |
30 |
31 |

32 |
33 |
34 |
$
35 |
36 | 42 |
43 |
44 |
45 |
46 | 49 |
50 | 51 |
52 | 53 | 54 | 55 |
56 | 57 |
58 | -------------------------------------------------------------------------------- /view/shop/checkout-done.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/shop-html.php', APP_ROOT); 7 | 8 | ?> 9 | 10 |
11 |

12 |
13 |

Complete

14 |
15 |
16 | 17 |
18 |
Order Complete! You will receive an email or phone confirmation shortly.
19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 | $b2b_item) { 28 | echo ''; 29 | printf('', $idx + 1, $b2b_item['product']['name']); 30 | printf('', $b2b_item['variety']['name']); 31 | printf(''; 35 | } 36 | ?> 37 |

%d: %s

%s

' 32 | , $b2b_item['qty'] 33 | ); 34 | echo '
38 | 39 |
40 | 41 |
42 |

Client Details

43 | 44 |
45 |
46 |
Name:
47 | 48 |
49 |
50 | 51 |
52 |
53 |
Email:
54 | 55 |
56 |
57 | 58 |
59 |
60 |
Phone:
61 | 62 |
63 |
64 | 65 |
66 | 67 |
68 | Shop Storefront 69 | 70 |
71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /view/shop/checkout.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/shop-html.php', APP_ROOT); 7 | 8 | ?> 9 | 10 |
11 |

12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 | $b2b_item) { 24 | echo ''; 25 | printf('', $idx + 1, $b2b_item['product']['name']); 26 | printf('', $b2b_item['variety']['name']); 27 | printf(''; 32 | } 33 | ?> 34 |

%d: %s

%s

' 28 | , $b2b_item['inventory_id'] 29 | , $b2b_item['qty'] 30 | ); 31 | echo '
35 | 36 |
37 | 38 |
39 |

Client Details

40 | 41 |
42 |
43 |
Name:
44 | 45 |
46 |
47 | 48 |
49 |
50 |
Email:
51 | 52 |
53 |
54 | 55 |
56 |
57 |
Phone:
58 | 59 |
60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 | 68 |
69 | 70 |
71 |
72 | -------------------------------------------------------------------------------- /view/shop/example.php: -------------------------------------------------------------------------------- 1 | layout_file = sprintf('%s/view/_layout/shop-html.php', APP_ROOT); 7 | 8 | ?> 9 | 10 |
11 |

12 |
13 | View Cart 14 |
15 |
16 | 17 |
18 | 21 |
22 |
23 |
24 |

25 |

26 |
27 |
28 |
29 |
30 |
31 | 37 |
38 |
39 |
40 | Inventory: 41 |
42 |
43 |
44 | 48 |
49 | 50 | 51 | 77 | -------------------------------------------------------------------------------- /view/shop/main.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openthc/pos/d54b19901e8d4e075a7a8802eaca36e1bf9ef7f6/view/shop/main.php -------------------------------------------------------------------------------- /webroot/.well-known/openthc/app: -------------------------------------------------------------------------------- 1 | app 2 | -------------------------------------------------------------------------------- /webroot/.well-known/openthc/pos: -------------------------------------------------------------------------------- 1 | pos 2 | -------------------------------------------------------------------------------- /webroot/css/shop.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Styles for the Public Shop and Example 3 | */ 4 | 5 | .product-grid { 6 | display: flex; 7 | flex-direction: row; 8 | flex-wrap: wrap; 9 | justify-content: space-around; 10 | } 11 | .product-grid .product-item-wrap { 12 | flex: 1 1 25%; 13 | margin: 0 0 0.25rem 0; 14 | max-width: 30rem; 15 | min-width: 20rem; 16 | padding: 0.25rem; 17 | } 18 | .product-grid .product-item-wrap .product-item { 19 | border: 1px solid #ddd; 20 | border-radius: 0.25rem; 21 | } 22 | .product-grid .product-item-wrap .product-item header { 23 | margin: 0; 24 | padding: 0.25rem 0.50rem; 25 | } 26 | .product-grid .product-item-wrap .product-item header h2 27 | , .product-grid .product-item-wrap .product-item header h3 { 28 | margin: 0; 29 | padding: 0; 30 | } 31 | 32 | .product-grid .product-item-wrap .product-item .product-cost { 33 | font-size: 1.5rem; 34 | font-weight: bold; 35 | } 36 | 37 | .product-grid .product-item-wrap .product-item .product-item-foot { 38 | display: flex; 39 | margin: 0.50rem; 40 | flex-direction: row; 41 | justify-content: space-between; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /webroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | POS || OpenTHC 9 | 29 | 30 | 31 | 32 |
33 | OpenTHC Icon 34 |
35 | 36 |

POS

37 |

/auth/open

38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /webroot/js/pos-camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Camera Integration 3 | * @see https://www.damirscorner.com/blog/posts/20170901-DetectingCameraPermissionDialogInBrowser.html 4 | */ 5 | 6 | (function(window) { 7 | 8 | 'use strict'; 9 | 10 | var Camera = null; 11 | var Camera_auth = 0; 12 | 13 | window.OpenTHC = window.OpenTHC || {}; 14 | window.OpenTHC.Camera = { 15 | 16 | /** 17 | * 18 | */ 19 | exists: function(callback) 20 | { 21 | let dev_list = navigator.mediaDevices; 22 | if (!dev_list || !dev_list.enumerateDevices) return false; 23 | dev_list.enumerateDevices().then(devices => { 24 | callback(devices.some(device => 'videoinput' === device.kind)); 25 | }); 26 | 27 | }, 28 | 29 | /** 30 | * Open the Camera 31 | */ 32 | open: function(callback) { 33 | 34 | if (!Camera) { 35 | navigator.mediaDevices 36 | .getUserMedia({ audio: false, video: true }) 37 | .then(function(stream) { 38 | Camera = stream; 39 | callback(stream); 40 | }) 41 | .catch(function(err) { console.log(err); }); 42 | } 43 | 44 | }, 45 | 46 | scan: function(callback) 47 | { 48 | // Read from the camera until we get something 49 | // something == PDF417, QR or C128 50 | 51 | // Wait for QR Code 52 | 53 | // But Also show the Scanning Video On the Screen Somwehere 54 | 55 | } 56 | 57 | }; 58 | 59 | })(window); 60 | -------------------------------------------------------------------------------- /webroot/js/pos-modal-cart-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [description] 3 | * @return {[type]} [description] 4 | */ 5 | 6 | 7 | $(function() { 8 | 9 | $('#pos-cart-option-save').on('click', function() { 10 | 11 | OpenTHC.POS.Cart.date = $('#cart-option-date').val(); 12 | OpenTHC.POS.Cart.time = $('#cart-option-time').val(); 13 | 14 | // var $b = $(this); 15 | // $b.html(' Working...'); 16 | // $b.attr('disabled', 'disabled'); 17 | 18 | // var arg = { 19 | // a: 'checkout-option', 20 | // date: $('#checkout-option-date').val(), 21 | // time: $('#checkout-option-time').val(), 22 | // }; 23 | 24 | // $.post('/pos/cart/ajax', arg, function(body, stat) { 25 | $('#pos-modal-cart-option').modal('hide'); 26 | // }); 27 | 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /webroot/js/pos-modal-discount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For the POS Modal for Discounts 3 | */ 4 | 5 | $(function() { 6 | 7 | var adj = 0; 8 | var adj_note = 'Applied Discount'; 9 | var base_price = 0; 10 | var full_price = 0; 11 | var item_taxes_total = 0; 12 | 13 | function _update_adj_new(adj) 14 | { 15 | $('.pos-checkout-sum-adj').html( (adj).toFixed(2) ); 16 | $('.pos-checkout-sum-new').html( (base_price + adj).toFixed(2) ); 17 | } 18 | 19 | // Do stuff on Modal Open 20 | $('#pos-modal-discount').on('show.bs.modal', function() { 21 | 22 | console.log('pos-modal-discount!show'); 23 | 24 | base_price = parseFloat($('.pos-checkout-sum').first().html()) || 0; 25 | 26 | // $('#pos-modal-discount-list').load('/pos/ajax', { a: 'discount-list' }); 27 | 28 | }); 29 | 30 | // Detect Change and Reset Other One (fix resets pct) 31 | $('#pos-checkout-discount-fix').on('blur keyup', function(e) { 32 | 33 | var val = this.value; 34 | var fix = Math.abs(parseFloat(val) || 0); 35 | $('#pos-checkout-discount-pct').val(''); 36 | 37 | adj = fix * -1; 38 | adj_note = `Applied Discount $${adj.toFixed(2)}`; 39 | _update_adj_new(adj); 40 | 41 | }); 42 | 43 | // Percent Discount 44 | $('#pos-checkout-discount-pct').on('blur keyup', function(e) { 45 | 46 | var val = this.value; 47 | var pct = Math.abs(parseFloat(val) || 0); 48 | $('#pos-checkout-discount-fix').val(''); 49 | 50 | if (pct <= 1) { 51 | pct = pct * 100; 52 | } 53 | adj = (base_price * pct / 100) * -1; 54 | adj_note = `Applied Discount ${pct.toFixed(0)}%`; 55 | 56 | _update_adj_new(adj); 57 | 58 | }); 59 | 60 | // Add Line Item 61 | $('#pos-discount-apply').on('click', function() { 62 | Cart_addItem({ 63 | id: window.ulid(), 64 | qty: 1, 65 | name: adj_note, 66 | price: adj, // v1 67 | unit_price: adj // v2 68 | }); 69 | }); 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /webroot/js/pos-modal-loyalty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [description] 3 | * @return {[type]} [description] 4 | */ 5 | 6 | 7 | $(function() { 8 | 9 | $('#pos-loyalty-apply').on('click', function() { 10 | 11 | var $b = $(this); 12 | $b.html(' Working...'); 13 | $b.attr('disabled', 'disabled'); 14 | 15 | var arg = { 16 | a: 'loyalty', 17 | phone: $('#loyalty-phone').val(), 18 | email: $('#loyalty-email').val(), 19 | other: $('#loyalty-other').val(), 20 | }; 21 | 22 | $.post('/pos/cart/ajax', arg, function(body, stat) { 23 | Cart_addItem({ 24 | id: 0, 25 | name: body.data.name, 26 | weight: '-', 27 | price: body.data.rank, 28 | size: 1, 29 | }); 30 | $('#pos-modal-loyalty').modal('hide'); 31 | }) 32 | 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /webroot/js/pos-modal-payment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript Handlers for the POS Payment Modal 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-only 5 | */ 6 | 7 | var ppCashPaid_List = new Array(); 8 | 9 | function ppFormUpdate() 10 | { 11 | var full = parseFloat(OpenTHC.POS.Cart.full_price); 12 | var cash = parseFloat($('#payment-cash-incoming').text()); 13 | var need = (full - cash); 14 | var back = (cash - full); 15 | 16 | console.log('ppFormUpdate(' + cash + ', ' + full + ')'); 17 | 18 | 19 | if (cash < full) { 20 | 21 | $('#amount-paid-wrap').removeClass('alert-success'); 22 | $('#amount-paid-wrap').addClass('alert-secondary'); 23 | 24 | $('#amount-need-wrap').removeClass('alert-danger alert-secondary alert-success'); 25 | $('#amount-need-wrap').addClass('alert-warning'); 26 | $('#amount-need-hint').text('Due:'); 27 | $('#payment-cash-outgoing').text(need.toFixed(2)); 28 | 29 | } else if (cash === full) { 30 | 31 | $('#amount-paid-wrap').removeClass('alert-secondary alert-success'); 32 | $('#amount-paid-wrap').addClass('alert-success'); 33 | 34 | $('#amount-need-wrap').removeClass('alert-danger alert-secondary alert-success alert-warning'); 35 | $('#amount-need-wrap').addClass('alert-success'); 36 | $('#amount-need-hint').text('Perfect!'); 37 | $('#payment-cash-outgoing').text('0.00'); 38 | 39 | // $('#payment-cash-incoming').addClass('text-success').removeClass('text-danger').removeClass('text-warning'); 40 | 41 | $('#pos-payment-commit').removeAttr('disabled'); 42 | 43 | } else if (cash > full) { 44 | 45 | $('#amount-paid-wrap').removeClass('alert-secondary alert-success'); 46 | $('#amount-paid-wrap').addClass('alert-success'); 47 | 48 | // Making Change 49 | $('#amount-need-wrap').removeClass('alert-danger alert-secondary alert-success'); 50 | $('#amount-need-wrap').addClass('alert-danger'); 51 | $('#amount-need-hint').text('Change:'); 52 | $('#payment-cash-outgoing').text( back.toFixed(2) ); 53 | 54 | // $('#payment-cash-incoming').addClass('text-warning').removeClass('text-danger').removeClass('text-success'); 55 | // $('#pp-card-pay').removeClass('text-danger').removeClass('text-success').removeClass('text-warning'); 56 | 57 | $('#pos-payment-commit').removeAttr('disabled'); 58 | 59 | } 60 | 61 | if (ppCashPaid_List.length == 0) { 62 | $('#pos-pay-undo').prop('disabled', true); 63 | } else { 64 | $('#pos-pay-undo').prop('disabled', false); 65 | } 66 | 67 | } 68 | 69 | 70 | function ppAddCash(n) 71 | { 72 | console.log('ppAddCash'); 73 | 74 | var full = OpenTHC.POS.Cart.full_price; 75 | 76 | var add = parseFloat( $(n).data('amount') ); 77 | var cur = parseFloat( $('#payment-cash-incoming').text() ); 78 | if (!cur) cur = 0; 79 | 80 | var cash = (cur + add); 81 | $('#payment-cash-incoming').text( cash.toFixed(2) ); 82 | 83 | var card = full - cash; 84 | $('#pp-card-pay').val( card.toFixed(2) ); 85 | 86 | ppCashPaid_List.push(add); 87 | 88 | ppFormUpdate(); 89 | 90 | } 91 | 92 | function ppAddCard() 93 | { 94 | console.log('ppAddCard'); 95 | 96 | var need = $('#payment-need').val(); 97 | $('#pp-card-confirm').val(need); 98 | 99 | //Weed.modal('shut'); 100 | 101 | // var arg = $('#psi-form').serializeArray(); 102 | // $('#modal-content-wrap').load('/pos/pay', arg, function() { 103 | // $.post('/pos/pay', arg, function(res) { 104 | 105 | // var x = $('#card-modal').clone(); 106 | // $(x).find('#pp-card-confirm').attr('id', 'pp-card-prompt'); 107 | //Weed.modal( $('#card-modal') ); 108 | //$('#card-modal').show(); 109 | 110 | 111 | // Weed.modal('#card-modal'); 112 | // $('#pp-card-confirm').val( $('#pp-card-pay').val() ); 113 | // Weed.modal('#card-modal'); 114 | // }); 115 | } 116 | 117 | $(function() { 118 | 119 | $('.pp-cash').on('click touchend', function(e) { 120 | ppAddCash(this); 121 | e.preventDefault(); 122 | e.stopPropagation(); 123 | return false; 124 | }); 125 | 126 | $('.pp-card').on('click touchend', function(e) { 127 | ppAddCard(); 128 | e.preventDefault(); 129 | e.stopPropagation(); 130 | return false; 131 | }); 132 | 133 | // Focus on Select! 134 | // $('#payment-cash-incoming').on('focus', function(e) { 135 | // console.log('focus'); 136 | // $(this).select(); 137 | // }); 138 | // $('#payment-cash-incoming').on('mouseup', function(e) { 139 | // console.log('mouseup'); 140 | // e.preventDefault(); 141 | // return false; 142 | // }); 143 | 144 | // $('#payment-cash-incoming').on('keyup', function() { 145 | // ppFormUpdate(); 146 | // }); 147 | 148 | // Reset my Form 149 | $('#pos-pay-undo').on('click', function() { 150 | $('#payment-cash-incoming').text('0.00'); 151 | $('#pp-card-pay').text('0.00'); 152 | ppFormUpdate(); 153 | }); 154 | 155 | $(document.body).on('click', '#pos-card-back', function() { 156 | //Weed.modal('shut'); 157 | }); 158 | 159 | $(document.body).on('click', '#pos-card-done', function() { 160 | //Weed.modal('shut'); 161 | }); 162 | 163 | /** 164 | Actual Payment Button 165 | */ 166 | $('#pos-payment-commit').on('click touchend', function(e) { 167 | 168 | var cash_incoming = $('#payment-cash-incoming').text(); 169 | var cash_outgoing = $('#payment-cash-outgoing').text(); 170 | 171 | // Append to existing form to capture all the other existing inputs 172 | $('#psi-form').attr('action', '/pos/checkout/commit'); 173 | $('#psi-form').append(''); 174 | $('#psi-form').append(``); 175 | $('#psi-form').append(``); 176 | $('#psi-form').append(``); 177 | $('#psi-form').append(''); 178 | $('#psi-form').append(``); 179 | $('#psi-form').append(``); 180 | $('#psi-form').submit(); 181 | 182 | return false; 183 | 184 | }); 185 | 186 | }); 187 | -------------------------------------------------------------------------------- /webroot/js/pos-printer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Print Helper 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-only 5 | */ 6 | 7 | POS.Printer = { 8 | 9 | /** 10 | * 11 | */ 12 | printLocalBrowser: function() 13 | { 14 | 15 | }, 16 | 17 | /** 18 | * Download the Document as PDF and then send to our HTTP Print Queue 19 | */ 20 | printLocalNetwork: function(pdf_url, printer_url) 21 | { 22 | console.log('POS.Printer.printLocalNetwork()'); 23 | 24 | // Big AJAX 25 | $.ajax({ 26 | type: 'POST', 27 | url: pdf_url, 28 | data: { 29 | a: 'send-print', 30 | }, 31 | dataType: 'binary', 32 | xhrFields: { 33 | responseType: 'blob', 34 | }, 35 | success: function(body, ret) { 36 | 37 | // Then another Big AJAX 38 | var FD = new FormData(); 39 | FD.append('a', 'print-file'); 40 | FD.append('file', body); 41 | 42 | $.ajax({ 43 | type: "POST", 44 | url: printer_url, 45 | cache: false, 46 | contentType: false, 47 | processData: false, 48 | data: FD, 49 | xhrFields:{ 50 | responseType: 'blob' 51 | }, 52 | }); 53 | } 54 | }); 55 | 56 | }, 57 | 58 | /** 59 | * 60 | */ 61 | printServerConnect: function() { 62 | 63 | 64 | }, 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /webroot/js/pos-scanner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * POS Scanner 3 | * Tries to be a Helper for reading PDF417 from Bluetooth or USB Scanner 4 | */ 5 | 6 | 7 | var POS = POS || {}; 8 | 9 | POS.Scanner = {}; 10 | POS.Scanner.callback = undefined; 11 | POS.Scanner.data = []; 12 | 13 | POS.Scanner.done = function() 14 | { 15 | var data = POS.Scanner.data.join(' '); 16 | data = data.replace(/Shift/g, ' '); 17 | data = data.replace(/Control J/g, '
--Control J--
'); 18 | data = data.replace(/\[CR\]$/g, ''); 19 | $('#scan-input-data').html(data); 20 | 21 | if (POS.Scanner.callback) { 22 | POS.Scanner.callback(POS.Scanner.data); 23 | POS.Scanner.callback = undefined; 24 | } 25 | } 26 | 27 | 28 | POS.Scanner.live = function(node, callback) 29 | { 30 | POS.Scanner.callback = callback; 31 | POS.Scanner.data = []; 32 | $(document.body).on('keydown', POS.Scanner.read); 33 | $(node).html('Ready to Scan'); 34 | 35 | } 36 | 37 | /** 38 | * Read A Character 39 | * @param {[type]} e [description] 40 | * @return {[type]} [description] 41 | */ 42 | POS.Scanner.read = function(e) 43 | { 44 | //console.log(e); 45 | 46 | var c = String.fromCharCode(e.which); 47 | 48 | console.log({ 49 | c: c, 50 | which: e.which, 51 | key: e.key, 52 | keyCode: e.keyCode, 53 | char: e.char, 54 | charCode: e.charCode, 55 | alt: e.altKey, 56 | ctrl: e.ctrlKey, 57 | meta: e.metaKey, 58 | shift: e.shiftKey, 59 | }); 60 | 61 | switch (e.which) { 62 | case 13: // Enter 63 | POS.Scanner.stop(); 64 | POS.Scanner.done(); 65 | break; 66 | case 16: // Shift 67 | case 17: // Ctrl 68 | case 18: // Alt 69 | c = e.key; 70 | break; 71 | case 91: // Windows Key 72 | case 93: // Windows Context Key 73 | return false; 74 | case 186: 75 | if (e.shiftKey) { 76 | c = ':'; 77 | } else { 78 | c = ';'; 79 | } 80 | break; 81 | case 190: 82 | c = '.'; 83 | if (e.shiftKey) { 84 | c = '>'; 85 | } 86 | break; 87 | case 191: // / 88 | c = '/'; 89 | if (e.shiftKey) { 90 | c = '?'; 91 | } 92 | break; 93 | case 192: 94 | if (e.shiftKey) { 95 | c = '~'; 96 | } else { 97 | c = '`'; 98 | } 99 | break; 100 | } 101 | 102 | //POS.Scanner.data.push(e.keyCode); 103 | POS.Scanner.data.push(c); 104 | return false; 105 | 106 | } 107 | 108 | POS.Scanner.stop = function() 109 | { 110 | $(document.body).off('keydown', POS.Scanner.read); 111 | } 112 | -------------------------------------------------------------------------------- /webroot/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading... 7 | 39 | 40 | 41 |
42 |
43 |

Loading...

44 |
45 |
46 | OpenTHC Icon 47 |
48 |
49 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /webroot/main.php: -------------------------------------------------------------------------------- 1 | . 18 | * 19 | * SPDX-License-Identifier: GPL-3.0-only 20 | */ 21 | 22 | require_once(dirname(dirname(__FILE__)) . '/boot.php'); 23 | 24 | // Slim Application 25 | $cfg = []; 26 | $cfg['debug'] = true; 27 | $app = new \OpenTHC\App($cfg); 28 | 29 | 30 | // Container 31 | $con = $app->getContainer(); 32 | $con['DB'] = function($c) { 33 | 34 | $dbc = null; 35 | 36 | if (!empty($_SESSION['dsn'])) { 37 | $dbc = _dbc($_SESSION['dsn']); 38 | } 39 | 40 | return $dbc; 41 | 42 | }; 43 | 44 | 45 | // Redis Connection 46 | $con['Redis'] = function($c) { 47 | $x = \OpenTHC\Config::get('redis/hostname'); 48 | $r = new \Redis(); 49 | $r->connect($x); 50 | return $r; 51 | }; 52 | 53 | 54 | // Get Current Company Object 55 | $con['Company'] = function($c0) { 56 | 57 | static $C; 58 | 59 | if (empty($C)) { 60 | 61 | $dbc = $c0->DB; 62 | $C = new \OpenTHC\Company($dbc, $_SESSION['Company']); 63 | 64 | } 65 | 66 | return $C; 67 | 68 | }; 69 | 70 | 71 | // API 72 | $app->group('/api/v2018', 'OpenTHC\POS\Module\API'); 73 | 74 | 75 | // Main Page 76 | $app->group('/dashboard', 'OpenTHC\POS\Module\Dashboard') 77 | ->add('OpenTHC\POS\Middleware\Auth') 78 | ->add('OpenTHC\Middleware\Session'); 79 | 80 | 81 | // POS / Register 82 | $app->group('/pos', 'OpenTHC\POS\Module\POS') 83 | ->add('OpenTHC\POS\Middleware\Auth') 84 | ->add('OpenTHC\Middleware\Session'); 85 | 86 | 87 | // CRM / Loyalty 88 | $app->group('/crm', 'OpenTHC\POS\Module\CRM') 89 | ->add('OpenTHC\POS\Middleware\Auth') 90 | ->add('OpenTHC\Middleware\Session'); 91 | 92 | 93 | // B2B Operations 94 | $app->group('/report', 'OpenTHC\POS\Module\Report') 95 | ->add('OpenTHC\POS\Middleware\Auth') 96 | ->add('OpenTHC\Middleware\Session'); 97 | 98 | 99 | // Onsite & Online menus 100 | $app->group('/menu', 'OpenTHC\POS\Module\Menu') 101 | ->add('OpenTHC\POS\Middleware\Auth') 102 | ->add('OpenTHC\Middleware\Session'); 103 | 104 | 105 | // External Shop 106 | $app->group('/shop', 'OpenTHC\POS\Module\Shop') 107 | ->add('OpenTHC\Middleware\Session'); 108 | 109 | 110 | // CRM / Loyalty 111 | $app->get('/contact/ajax', 'OpenTHC\POS\Controller\Contact') 112 | ->add('OpenTHC\POS\Middleware\Auth') 113 | ->add('OpenTHC\Middleware\Session'); 114 | 115 | // Vendor 116 | // $app->group('/vendor', 'OpenTHC\POS\Module\Vendor') 117 | // ->add('OpenTHC\POS\Middleware\Auth') 118 | // ->add('OpenTHC\Middleware\Session'); 119 | 120 | 121 | // Authentication 122 | $app->group('/auth', function() { 123 | $this->get('', 'OpenTHC\POS\Controller\Auth\oAuth2\Open'); 124 | $this->get('/open', 'OpenTHC\POS\Controller\Auth\oAuth2\Open'); 125 | $this->get('/back', 'OpenTHC\POS\Controller\Auth\oAuth2\Back'); 126 | $this->get('/init', 'OpenTHC\POS\Controller\Auth\Init')->setName('auth/init'); 127 | $this->get('/ping', 'OpenTHC\Controller\Auth\Ping'); 128 | $this->get('/shut', 'OpenTHC\POS\Controller\Auth\Shut'); 129 | }) 130 | ->add('OpenTHC\Middleware\Session'); 131 | 132 | 133 | // Intent 134 | $app->map(['GET','POST'], '/intent', 'OpenTHC\POS\Controller\Intent') 135 | ->add('OpenTHC\Middleware\Session'); 136 | 137 | 138 | // Webhooks 139 | $app->group('/webhook', 'OpenTHC\POS\Module\Webhook'); 140 | 141 | 142 | // Execute 143 | $app->run(); 144 | 145 | exit(0); 146 | -------------------------------------------------------------------------------- /webroot/robots.txt: -------------------------------------------------------------------------------- 1 | user-agent: * 2 | disallow: / 3 | --------------------------------------------------------------------------------