├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── src
├── admin
│ ├── controller
│ │ └── extension
│ │ │ └── module
│ │ │ └── vsbridge.php
│ ├── language
│ │ └── en-gb
│ │ │ └── extension
│ │ │ └── module
│ │ │ └── vsbridge.php
│ └── view
│ │ └── template
│ │ └── extension
│ │ └── module
│ │ └── vsbridge.tpl
├── catalog
│ ├── controller
│ │ └── vsbridge
│ │ │ ├── attributes.php
│ │ │ ├── auth.php
│ │ │ ├── cart.php
│ │ │ ├── categories.php
│ │ │ ├── order.php
│ │ │ ├── product.php
│ │ │ ├── products.php
│ │ │ ├── stock.php
│ │ │ ├── sync_session.php
│ │ │ ├── taxrules.php
│ │ │ └── user.php
│ ├── language
│ │ ├── en-gb
│ │ │ └── vsbridge
│ │ │ │ └── api.php
│ │ └── hu-hu
│ │ │ └── vsbridge
│ │ │ └── api.php
│ └── model
│ │ └── vsbridge
│ │ └── api.php
└── system
│ └── engine
│ └── vsbridgecontroller.php
└── tests
└── test.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | composer.lock
3 | vendor
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Butopêa.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Storefront Connector Extension for OpenCart
2 | **Compatible with: OpenCart 2.3.0.2**
3 |
4 | **API Base URL:** `https://site_url/vsbridge/`
5 |
6 | **API Credentials:**
7 |
8 | - Username: OC API Name
9 | - Password: OC API Key
10 | - Secret Key: Must be generated in VS Bridge module settings
11 | - Token Format: JWT
12 |
13 | **Installation:**
14 |
15 | * Add the following line in the extra section of your OpenCart's composer.json:
16 |
17 | **Make sure to change the destination folder to match your upload/public folder.**
18 | ```json
19 | "extra": {
20 | "filescopier": [
21 | {
22 | "source": "vendor/butopea/vue-storefront-opencart-vsbridge/src",
23 | "destination": "upload",
24 | "debug": "true"
25 | }
26 | ]
27 | }
28 | ```
29 |
30 | Notes about the `source` and `destination` paths:
31 |
32 | > * The destination element must be a folder. if the destination folder does not exists, it is recursively created using `mkdir($destination, 0755, true)`
33 | > * If the destination folder is not an absolute path, the relative path is calculated using the vendorDir path (`$project_path = \realpath($this->composer->getConfig()->get('vendor-dir').'/../').'/'`;)
34 | > * The source element is evaluated using the php function `\glob($source, GLOB_MARK)` and a recursive copy is made for every result of this function into the destination folder
35 |
36 | * Run the following command to add the required composer packages, including the VS Bridge itself:
37 | ```bash
38 | composer require butopea/vue-storefront-opencart-vsbridge
39 | ```
40 |
41 | * Add the URL rewrite rule for VS Bridge (Nginx example):
42 | ```nginx
43 | location /vsbridge {
44 | rewrite ^/(.+)$ /index.php?route=$1 last;
45 | }
46 | ```
47 | * Install the extension in OpenCart (Extensions -> Modules) and generate a secret key
48 |
49 | This extension will create its own tables in the database. An overview of the database changes [can be found here](https://github.com/butopea/vue-storefront-opencart-vsbridge/blob/master/src/admin/controller/extension/module/vsbridge.php#L103).
50 |
51 | * Get the [Vue Storefront OpenCart Indexer](https://github.com/butopea/vue-storefront-opencart-indexer) to import your data into ElasticSearch.
52 |
53 | *You need to whitelist your indexer's IP address in OpenCart at oc_url/admin/index.php?route=user/api*
54 |
55 | **Tests:**
56 |
57 | * Edit `tests/test.php` and add the credentials and settings
58 | * Run `php tests/test.php`
59 |
60 | **Development:**
61 |
62 | We're currently in the early stages of getting all the features working and would love other OpenCart developers to join in with us on this project!
63 |
64 | If you found a bug or want to contribute toward making this extension better, please fork this repository, make your changes, and make a pull request.
65 |
66 | **Credits:**
67 |
68 | Made with ❤ by [Butopêa](https://butopea.com)
69 |
70 | **Support:**
71 |
72 |
73 | Please ask your questions regarding this extension on Vue Storefront's Slack https://vuestorefront.slack.com/ You can join via [this invitation link](https://slack.vuestorefront.io/).
74 |
75 | **License:**
76 |
77 | This extension is completely free and released under the MIT License.
78 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "butopea/vue-storefront-opencart-vsbridge",
3 | "type": "opencart-extension",
4 | "description": "Vue Storefront Connector Extension for OpenCart",
5 | "keywords": ["vue-storefront", "opencart", "vsbridge"],
6 | "homepage": "https://github.com/butopea",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Mohammad Tomaraei",
11 | "email": "themreza@gmail.com",
12 | "homepage": "https://github.com/themreza",
13 | "role": "Developer"
14 | }
15 | ],
16 | "require": {
17 | "rbdwllr/reallysimplejwt": "2.0.2",
18 | "voku/urlify": "5.0.2",
19 | "butopea/composer-plugin-filecopier": "^1.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/admin/controller/extension/module/vsbridge.php:
--------------------------------------------------------------------------------
1 | load->language('extension/module/vsbridge');
7 |
8 | $this->document->setTitle($this->language->get('heading_title'));
9 |
10 | $this->load->model('setting/setting');
11 |
12 | if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
13 | $this->model_setting_setting->editSetting('vsbridge', $this->request->post);
14 |
15 | $this->session->data['success'] = $this->language->get('text_success');
16 |
17 | $this->response->redirect($this->url->link('extension/extension', 'token=' . $this->session->data['token'] . '&type=module', true));
18 | }
19 |
20 | $data['heading_title'] = $this->language->get('heading_title');
21 |
22 | $data['text_edit'] = $this->language->get('text_edit');
23 | $data['text_enabled'] = $this->language->get('text_enabled');
24 | $data['text_disabled'] = $this->language->get('text_disabled');
25 |
26 | $data['entry_status'] = $this->language->get('entry_status');
27 | $data['entry_secret_key'] = $this->language->get('entry_secret_key');
28 | $data['entry_endpoint_statuses'] = $this->language->get('entry_endpoint_statuses');
29 |
30 | $data['info_secret_key'] = $this->language->get('info_secret_key');
31 | $data['info_endpoint_statuses'] = $this->language->get('info_endpoint_statuses');
32 |
33 | $data['button_save'] = $this->language->get('button_save');
34 | $data['button_cancel'] = $this->language->get('button_cancel');
35 | $data['button_generate_secret_key'] = $this->language->get('button_generate_secret_key');
36 |
37 | if (isset($this->error['warning'])) {
38 | $data['error_warning'] = $this->error['warning'];
39 | } else {
40 | $data['error_warning'] = '';
41 | }
42 |
43 | if (isset($this->error['secret_key'])) {
44 | $data['error_secret_key'] = $this->error['secret_key'];
45 | } else {
46 | $data['error_secret_key'] = '';
47 | }
48 |
49 | $data['breadcrumbs'] = array();
50 |
51 | $data['breadcrumbs'][] = array(
52 | 'text' => $this->language->get('text_home'),
53 | 'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], true)
54 | );
55 |
56 | $data['breadcrumbs'][] = array(
57 | 'text' => $this->language->get('text_module'),
58 | 'href' => $this->url->link('extension/extension', 'token=' . $this->session->data['token'] . '&type=module', true)
59 | );
60 |
61 | $data['breadcrumbs'][] = array(
62 | 'text' => $this->language->get('heading_title'),
63 | 'href' => $this->url->link('extension/module/vsbridge', 'token=' . $this->session->data['token'], true)
64 | );
65 |
66 | $data['action'] = $this->url->link('extension/module/vsbridge', 'token=' . $this->session->data['token'], true);
67 |
68 | $data['cancel'] = $this->url->link('extension/extension', 'token=' . $this->session->data['token'] . '&type=module', true);
69 |
70 | $default_endpoint_statuses = array(
71 | 'attributes' => true,
72 | 'auth' => true,
73 | 'cart' => true,
74 | 'categories' => true,
75 | 'order' => true,
76 | 'product' => true,
77 | 'products' => true,
78 | 'stock' => true,
79 | 'sync_session' => true,
80 | 'taxrules' => true,
81 | 'user' => true
82 | );
83 |
84 | $data['vsbridge_status'] = $this->request->post['vsbridge_status'] ?? $this->config->get('vsbridge_status');
85 |
86 | $data['vsbridge_secret_key'] = $this->request->post['vsbridge_secret_key'] ?? $this->config->get('vsbridge_secret_key');
87 |
88 | if (isset($this->request->post['vsbridge_endpoint_statuses'])) {
89 | $data['vsbridge_endpoint_statuses'] = $this->request->post['vsbridge_endpoint_statuses'];
90 | } elseif(!empty($this->config->get('vsbridge_endpoint_statuses'))) {
91 | $data['vsbridge_endpoint_statuses'] = $this->config->get('vsbridge_endpoint_statuses');
92 | } else {
93 | $data['vsbridge_endpoint_statuses'] = $default_endpoint_statuses;
94 | }
95 |
96 | $data['header'] = $this->load->controller('common/header');
97 | $data['column_left'] = $this->load->controller('common/column_left');
98 | $data['footer'] = $this->load->controller('common/footer');
99 |
100 | $this->response->setOutput($this->load->view('extension/module/vsbridge', $data));
101 | }
102 |
103 | protected function validate() {
104 | if (!$this->user->hasPermission('modify', 'extension/module/vsbridge')) {
105 | $this->error['warning'] = $this->language->get('error_permission');
106 | }
107 |
108 | if (empty($this->request->post['vsbridge_secret_key'])) {
109 | $this->error['secret_key'] = $this->language->get('error_secret_key');
110 | }else{
111 | if(!preg_match('/^.*(?=.{12,}+)(?=.*[0-9]+)(?=.*[A-Z]+)(?=.*[a-z]+)(?=.*[\*&!@%\^#\$]+).*$/', $this->request->post['vsbridge_secret_key'])){
112 | $this->error['secret_key'] = $this->language->get('error_secret_key');
113 | }
114 | }
115 |
116 | return !$this->error;
117 | }
118 |
119 | public function install()
120 | {
121 | $this->db->query("CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "vsbridge_token` (
122 | `vsbridge_token_id` int(11) NOT NULL,
123 | `customer_id` int(11) NOT NULL,
124 | `token` varchar(32) NOT NULL,
125 | `ip` varchar(40) NOT NULL,
126 | `timestamp` int(11) NOT NULL
127 | ) ENGINE = InnoDB DEFAULT CHARSET = utf8;");
128 |
129 | $this->db->query("ALTER TABLE
130 | `" . DB_PREFIX . "vsbridge_token`
131 | ADD
132 | PRIMARY KEY (`vsbridge_token_id`),
133 | ADD
134 | UNIQUE KEY `token` (`token`);");
135 |
136 | $this->db->query("ALTER TABLE
137 | `" . DB_PREFIX . "vsbridge_token` MODIFY `vsbridge_token_id` int(11) NOT NULL AUTO_INCREMENT;");
138 |
139 | $this->db->query("CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "vsbridge_refresh_token` (
140 | `vsbridge_refresh_token_id` int(11) NOT NULL,
141 | `customer_id` int(11) NOT NULL,
142 | `ip` varchar(40) NOT NULL,
143 | `timestamp` int(11) NOT NULL
144 | ) ENGINE = InnoDB DEFAULT CHARSET = utf8;");
145 |
146 | $this->db->query("ALTER TABLE
147 | `" . DB_PREFIX . "vsbridge_refresh_token`
148 | ADD
149 | PRIMARY KEY (`vsbridge_refresh_token_id`);");
150 |
151 | $this->db->query("ALTER TABLE
152 | `" . DB_PREFIX . "vsbridge_refresh_token` MODIFY `vsbridge_refresh_token_id` int(11) NOT NULL AUTO_INCREMENT;");
153 |
154 | $this->db->query("CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "vsbridge_session` (
155 | `customer_id` int(11) NOT NULL,
156 | `store_id` int(11) NOT NULL,
157 | `session_id` varchar(32) NOT NULL
158 | ) ENGINE = InnoDB DEFAULT CHARSET = utf8;");
159 |
160 | $this->db->query("ALTER TABLE
161 | `" . DB_PREFIX . "vsbridge_session`
162 | ADD
163 | UNIQUE `unique_index`(`customer_id`, `store_id`);");
164 | }
165 |
166 | public function uninstall() {
167 | $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "vsbridge_token`");
168 | $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "vsbridge_refresh_token`");
169 | $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "vsbridge_session`");
170 | }
171 | }
--------------------------------------------------------------------------------
/src/admin/language/en-gb/extension/module/vsbridge.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
80 |
81 |
82 |
83 |
84 |
137 |
138 |
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/attributes.php:
--------------------------------------------------------------------------------
1 | validateToken($this->getParam('apikey'));
19 |
20 | $language_id = $this->language_id;
21 |
22 | $this->load->model('vsbridge/api');
23 |
24 | $response = array();
25 |
26 | // Add the actual attributes
27 | $attributes = $this->model_vsbridge_api->getAttributes($language_id);
28 |
29 | foreach($attributes as $attribute){
30 | // To avoid the conflict of attribute IDs and filter IDs, an offset of 10000 is added to attribute IDs and attribute group IDs
31 | $attribute['attribute_id'] = ((int) $attribute['attribute_id']) + 10000;
32 | $attribute['attribute_group_id'] = ((int) $attribute['attribute_group_id']) + 10000;
33 |
34 |
35 | // We've also added an attribute_group_id field to accompany the /groups endpoint below
36 | array_push($response, array(
37 | 'attribute_code' => 'attribute_'.$attribute['attribute_id'],
38 | 'frontend_input' => 'text',
39 | 'frontend_label' => $attribute['name'],
40 | 'default_frontend_label' => $attribute['name'],
41 | 'is_user_defined' => true,
42 | 'is_unique' => false,
43 | 'attribute_id' => $attribute['attribute_id'],
44 | 'is_visible' => true,
45 | 'is_comparable' => true,
46 | 'is_visible_on_front' => true,
47 | 'position' => 0,
48 | 'id' => $attribute['attribute_id'],
49 | 'options' => array(),
50 | 'attribute_group_id' => (int) $attribute['attribute_group_id']
51 | ));
52 | }
53 |
54 | // Add filters as hidden, searchable attributes
55 | $filter_groups = $this->model_vsbridge_api->getFilterGroups($language_id);
56 |
57 | foreach($filter_groups as $filter_group){
58 | $options = array();
59 |
60 | $filters = $this->model_vsbridge_api->getFilters($filter_group['filter_group_id'], $language_id);
61 |
62 | foreach($filters as $filter){
63 | array_push($options, array(
64 | 'value' => (int) $filter['filter_id'],
65 | 'label' => trim($filter['name'])
66 | ));
67 | }
68 |
69 | array_push($response, array(
70 | 'attribute_code' => 'filter_group_'.$filter_group['filter_group_id'],
71 | 'frontend_input' => 'select',
72 | 'frontend_label' => $filter_group['name'],
73 | 'default_frontend_label' => $filter_group['name'],
74 | 'is_user_defined' => true,
75 | 'is_unique' => true,
76 | 'attribute_id' => (int) $filter_group['filter_group_id'],
77 | 'is_visible' => true,
78 | 'is_comparable' => true,
79 | 'is_visible_on_front' => true,
80 | 'position' => (int) $filter_group['sort_order'],
81 | 'id' => (int) $filter_group['filter_group_id'],
82 | 'options' => $options
83 | ));
84 | }
85 |
86 | $this->result = $response;
87 |
88 | $this->sendResponse();
89 | }
90 |
91 | /*
92 | * [Note: This endpoint is custom-made and is not required by Vue Storefront.]
93 | *
94 | * GET /vsbridge/attributes/groups
95 | * This method is used to get all of the attribute groups in OpenCart.
96 | *
97 | * GET PARAMS:
98 | * apikey - authorization key provided by /vsbridge/auth/admin endpoint
99 | */
100 | public function groups() {
101 | $this->validateToken($this->getParam('apikey'));
102 |
103 | $language_id = $this->language_id;
104 |
105 | $this->load->model('vsbridge/api');
106 |
107 | $response = array();
108 |
109 | // Retrieve the attribute groups
110 | $attribute_groups = $this->model_vsbridge_api->getAttributeGroups($language_id);
111 |
112 | foreach($attribute_groups as $attribute_group){
113 |
114 | // To avoid the conflict of attribute group IDs and filter group IDs, an offset of 10000 is added
115 | $attribute_group['attribute_group_id'] = ((int) $attribute_group['attribute_group_id']) + 10000;
116 |
117 | array_push($response, array(
118 | 'frontend_label' => $attribute_group['name'],
119 | 'is_visible' => true,
120 | 'position' => (int) $attribute_group['sort_order'],
121 | 'id' => $attribute_group['attribute_group_id'],
122 | 'attribute_group_id' => $attribute_group['attribute_group_id']
123 | ));
124 | }
125 |
126 | $this->result = $response;
127 |
128 | $this->sendResponse();
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/auth.php:
--------------------------------------------------------------------------------
1 | getPost();
19 |
20 | if(!empty($post['username']) && !empty($post['password'])){
21 |
22 | /* Retrieve OpenCart API */
23 | $this->load->model('account/api');
24 | $api_info = $this->model_account_api->getApiByKey($post['password']);
25 |
26 | /* Authenticate username and password */
27 | if($this->checkIndexValue($api_info, 'name', $post['username'])){
28 |
29 | /* Check if the IP is whitelisted */
30 | $whitelisted_ips = $this->model_account_api->getApiIps($api_info['api_id']);
31 | $client_ip = $this->getClientIp();
32 | $whitelisted = false;
33 |
34 | foreach($whitelisted_ips as $whitelisted_ip){
35 | if($this->checkIndexValue($whitelisted_ip, 'ip', $client_ip, 'trim')){
36 | $whitelisted = true;
37 | }
38 | }
39 |
40 | if($whitelisted == true){
41 |
42 | /* Create a new session ID and store it in OC API */
43 | $session_id = $this->session->createId();
44 | $this->session->start('api', $session_id);
45 | $this->session->data['api_id'] = $api_info['api_id'];
46 |
47 | $oc_token = $this->model_account_api->addApiSession($api_info['api_id'], $session_id, $client_ip);
48 |
49 | /* Generate a JWT token based on the OC token */
50 | $this->result = $this->getToken($oc_token);
51 |
52 | }else{
53 | $this->code = 401;
54 | $this->result = "Authentication failed. Your IP (".$client_ip.") is not whitelisted.";
55 | }
56 |
57 | }else{
58 | $this->code = 401;
59 | $this->result = "Authentication failed. Invalid username and/or password.";
60 | }
61 |
62 | }else{
63 | $this->code = 400;
64 | $this->result = "Invalid request. Missing username and/or password.";
65 | }
66 |
67 | $this->sendResponse();
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/cart.php:
--------------------------------------------------------------------------------
1 | apply_coupon();
15 | break;
16 | case 'delete-coupon':
17 | $this->delete_coupon();
18 | break;
19 | case 'payment-methods':
20 | $this->payment_methods();
21 | break;
22 | case 'shipping-methods':
23 | $this->shipping_methods();
24 | break;
25 | case 'shipping-information':
26 | $this->shipping_information();
27 | break;
28 | case 'collect-totals':
29 | $this->collect_totals();
30 | break;
31 | }
32 | }
33 |
34 | /*
35 | * POST /vsbridge/cart/create
36 | * This method is used to get all the products from the backend.
37 | *
38 | * WHEN:
39 | * This method is called when new Vue Storefront shopping cart is created.
40 | * First visit, page refresh, after user-authorization ...
41 | * If the token GET parameter is provided it's called as logged-in user; if not - it's called as guest-user.
42 | * To draw the difference - let's keep to Magento example.
43 | * For guest user vue-storefront-api is subsequently operating on /guest-carts API endpoints and for authorized users on /carts/ endpoints.
44 | *
45 | * GET PARAMS:
46 | * token - null OR user token obtained from /vsbridge/user/login
47 | *
48 | * Note: the cartId for authorized customers is NOT an integer due to OpenCart architecture. If it causes a problem in the future, create a table and map session_ids to integers row ids.
49 | */
50 |
51 | public function create(){
52 | $token = $this->getParam('token', true);
53 |
54 | /*
55 | * Generate a new session_id (just a value for the column, not a real session) to be inserted in the cart table.
56 | * The session_ids are marked with a vs_ prefix to be distinguished from real OpenCart sessions.
57 | * We can't use the API token because it expires and changes.
58 | *
59 | * For authenticated users, if there's a row in the cart table matching the customer_id, we will retrieve the saved session_id and not create a new one.
60 | */
61 |
62 | if(!empty($token) && $customer_info = $this->validateCustomerToken($token)){
63 | /* Authenticated customer */
64 | $this->load->model('vsbridge/api');
65 | $cart_id = $this->getSessionId($customer_info['customer_id']);
66 | }else{
67 | /* Guest */
68 | $cart_id = $this->getSessionId();
69 | }
70 |
71 | $this->result = $cart_id;
72 |
73 | $this->sendResponse();
74 | }
75 |
76 | /*
77 | * GET /vsbridge/cart/pull
78 | * Method used to fetch the current server side shopping cart content, used mostly for synchronization purposes when config.cart.synchronize=true
79 | *
80 | * WHEN:
81 | * This method is called just after any Vue Storefront cart modification to check if the server or client shopping cart items need to be updated.
82 | * It gets the current list of the shopping cart items. The synchronization algorithm in VueStorefront determines if server or client items need to be updated and executes api/cart/update or api/cart/delete accordngly.
83 | *
84 | * GET PARAMS:
85 | * token - null OR user token obtained from /vsbridge/user/login
86 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
87 | *
88 | * Note: Currently, all our products are non-configurable so the product_type is set manually to simple.
89 | * Remember to change it here too if we implement product options in the importer.
90 | */
91 |
92 | public function pull(){
93 | $token = $this->getParam('token', true);
94 | $cart_id = $this->getParam('cartId');
95 |
96 | $this->load->model('vsbridge/api');
97 |
98 | if($this->validateCartId($cart_id, $token)){
99 | $cart = $this->cart->getProducts();
100 |
101 | $response = array();
102 |
103 | /* If the cart exists and has items, retrieve it */
104 | if(!empty($cart)){
105 | foreach($cart as $cart_product){
106 | if(isset($cart_product['product_id']) && isset($cart_product['quantity'])){
107 | $product_info = $this->model_vsbridge_api->getProductDetails($cart_product['product_id'], $this->language_id);
108 | if (!$cart_product['stock']) {
109 | // Sending an HTTP-500 error causes a bug where VSF continuously makes new tokens on each page refresh
110 | // A quick fix is to remove any out of stock products from the cart to avoid this issue
111 | $this->cart->remove($cart_product['cart_id']);
112 | }
113 | $response[] = array(
114 | 'item_id' => (int)$cart_product['cart_id'],
115 | 'sku' => $product_info['sku'],
116 | 'model' => $product_info['model'],
117 | 'qty' => (int)$cart_product['quantity'],
118 | 'name' => $product_info['name'],
119 | 'price' => (float)$product_info['price'],
120 | 'product_type' => 'simple',
121 | 'quote_id' => $cart_id
122 | );
123 | }
124 | }
125 | }
126 |
127 | $this->result = $response;
128 | }
129 |
130 | $this->sendResponse();
131 | }
132 |
133 | /*
134 | * POST /vsbridge/cart/update
135 | * Method used to add or update shopping cart item's server side.
136 | * As a request body there should be JSON given representing the cart item, sku, and qty are the two required options.
137 | * If you like to update/edit server cart item you need to pass item_id (cart_id in OpenCart) identifier as well (can be optainted from api/cart/pull).
138 | *
139 | * WHEN:
140 | * This method is called just after api/cart/pull as a consequence of the synchronization process.
141 | *
142 | * GET PARAMS:
143 | * token - null OR user token obtained from /vsbridge/user/login
144 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
145 | *
146 | * REQUEST BODY:
147 | *
148 | * "cartItem":{
149 | * "sku":"WS12-XS-Orange",
150 | * "qty":1,
151 | * "product_option":{
152 | * "extension_attributes":{
153 | * "custom_options":[
154 | *
155 | * ],
156 | * "configurable_item_options":[
157 | * {
158 | * "option_id":"93",
159 | * "option_value":"56"
160 | * },
161 | * {
162 | * "option_id":"142",
163 | * "option_value":"167"
164 | * }
165 | * ],
166 | * "bundle_options":[
167 | *
168 | * ]
169 | * }
170 | * },
171 | * "quoteId":"0a8109552020cc80c99c54ad13ef5d5a"
172 | * }
173 | *
174 | * Note:
175 | * quoteId is specific to magento and is the same as the cartId. We currently don't perform any checks on it.
176 | * We haven't implemented product_option due to products being of type 'single'.
177 | */
178 |
179 | public function update(){
180 | $token = $this->getParam('token', true);
181 | $cart_id = $this->getParam('cartId');
182 | $input = $this->getPost();
183 |
184 | $this->load->model('vsbridge/api');
185 |
186 | if($this->validateCartId($cart_id, $token)){
187 | if(empty($input['cartItem'])) {
188 | $this->load->language('vsbridge/api');
189 | $this->code = 500;
190 | $this->result = $this->language->get('error_missing_cart_item');
191 | $this->sendResponse();
192 | }
193 |
194 | if(empty($input['cartItem']['sku'])){
195 | $this->load->language('vsbridge/api');
196 | $this->code = 500;
197 | $this->result = $this->language->get('error_missing_sku_qty');
198 | $this->sendResponse();
199 | }
200 |
201 | $input['cartItem']['qty'] = (int) $input['cartItem']['qty'];
202 |
203 | /* quantity must be greater than or equal to 1 */
204 | /* the delete endpoint is used for removing the item from the cart */
205 | if($input['cartItem']['qty'] < 1){
206 | $input['cartItem']['qty'] = 1;
207 | }
208 |
209 | $product_info = $this->model_vsbridge_api->getProductBySku($input['cartItem']['sku'], $this->language_id);
210 |
211 | if(empty($product_info)){
212 | $this->load->language('vsbridge/api');
213 | $this->code = 500;
214 | $this->result = $this->language->get('error_invalid_sku');
215 | $this->sendResponse();
216 | }
217 |
218 | // Check if the requested quantity is available on the selected product
219 | if(intval($input['cartItem']['qty']) > intval($product_info['quantity'])){
220 | $this->load->language('vsbridge/api');
221 | $this->code = 500;
222 | $this->result = $this->language->get('error_out_of_stock').' ['.$product_info['model'].'] '.$product_info['name'];
223 | $this->sendResponse();
224 | }
225 |
226 | // Check if the requested quantity meets the minimum (and multiple of minimum) product quantity
227 | if($product_info['minimum']){
228 | if (intval($input['cartItem']['qty']) < intval($product_info['minimum'])) {
229 | $this->load->language('vsbridge/api');
230 | $this->code = 500;
231 | $this->result = sprintf($this->language->get('error_minimum_product_quantity'), $product_info['model'], $product_info['minimum']);
232 | $this->sendResponse();
233 | }
234 |
235 | $qty_multiple = intval($input['cartItem']['qty']) / intval($product_info['minimum']);
236 | if (!is_int($qty_multiple)) {
237 | $this->load->language('vsbridge/api');
238 | $this->code = 500;
239 | $this->result = sprintf($this->language->get('error_minimum_multiple_product_quantity'), $product_info['model'], $product_info['minimum']);
240 | $this->sendResponse();
241 | }
242 | }
243 |
244 | if(isset($input['cartItem']['item_id'])){
245 | $this->cart->update($input['cartItem']['item_id'], $input['cartItem']['qty']);
246 | }else{
247 | $this->cart->add($product_info['product_id'], $input['cartItem']['qty']);
248 | }
249 |
250 | $cart_products = $this->cart->getProducts();
251 |
252 | $response = array();
253 |
254 | foreach($cart_products as $cart_product){
255 | if($cart_product['product_id'] == $product_info['product_id']){
256 | $response = array(
257 | 'item_id' => (int) $cart_product['cart_id'],
258 | 'sku' => $cart_product['model'],
259 | 'qty' => (int) $cart_product['quantity'],
260 | 'name' => $cart_product['name'],
261 | 'price' => (float) $cart_product['price'],
262 | 'product_type' => 'simple',
263 | 'quote_id' => $cart_id
264 | );
265 | }
266 | }
267 |
268 | $this->result = $response;
269 | }
270 |
271 | $this->sendResponse();
272 | }
273 |
274 |
275 | /*
276 | * POST /vsbridge/cart/delete
277 | * This method is used to remove the shopping cart item on server side.
278 | *
279 | * WHEN:
280 | * This method is called just after api/cart/pull as a consequence of the synchronization process
281 | *
282 | * GET PARAMS:
283 | * token - null OR user token obtained from /vsbridge/user/login
284 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
285 | *
286 | * REQUEST BODY:
287 | *
288 | * {
289 | * "cartItem":
290 | * {
291 | * "sku":"MS10-XS-Black",
292 | * "item_id":5853,
293 | * "quoteId":"81668"
294 | * }
295 | * }
296 | *
297 | */
298 |
299 | public function delete(){
300 | $token = $this->getParam('token', true);
301 | $cart_id = $this->getParam('cartId');
302 | $input = $this->getPost();
303 |
304 | $this->load->model('vsbridge/api');
305 |
306 | if($this->validateCartId($cart_id, $token)) {
307 | if (empty($input['cartItem'])) {
308 | $this->load->language('vsbridge/api');
309 | $this->code = 500;
310 | $this->result = $this->language->get('error_missing_cart_item');
311 | $this->sendResponse();
312 | }
313 |
314 | if (empty($input['cartItem']['sku']) || !isset($input['cartItem']['item_id'])) {
315 | $this->load->language('vsbridge/api');
316 | $this->code = 500;
317 | $this->result = $this->language->get('error_missing_sku_item_id');
318 | $this->sendResponse();
319 | }
320 |
321 |
322 |
323 | $product_info = $this->model_vsbridge_api->getProductBySku($input['cartItem']['sku'], $this->language_id);
324 |
325 | if(empty($product_info)){
326 | $this->load->language('vsbridge/api');
327 | $this->code = 500;
328 | $this->result = $this->language->get('error_invalid_sku');
329 | $this->sendResponse();
330 | }
331 |
332 | $this->cart->remove($input['cartItem']['item_id']);
333 |
334 | $this->result = true;
335 | }
336 |
337 | $this->sendResponse();
338 | }
339 |
340 |
341 | /*
342 | * POST /vsbridge/cart/apply-coupon
343 | * This method is used to apply the discount code to the current server side quote.
344 | *
345 | * GET PARAMS:
346 | * token - null OR user token obtained from /vsbridge/user/login
347 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
348 | * coupon - coupon code to apply
349 | */
350 |
351 | public function apply_coupon(){
352 | $token = $this->getParam('token', true);
353 | $cart_id = $this->getParam('cartId');
354 | $coupon = $this->getParam('coupon');
355 |
356 | $this->load->model('extension/total/coupon');
357 |
358 | if($this->validateCartId($cart_id, $token)) {
359 | $coupon_info = $this->model_extension_total_coupon->getCoupon($coupon);
360 |
361 | if(!$coupon_info){
362 | $this->load->language('extension/total/coupon');
363 | $this->code = 500;
364 | $this->result = $this->language->get('error_coupon');
365 | $this->sendResponse();
366 | }
367 |
368 | $this->session->data['coupon'] = $coupon;
369 | $this->result = true;
370 | }
371 |
372 | $this->sendResponse();
373 | }
374 |
375 | /*
376 | * POST /vsbridge/cart/delete-coupon
377 | * This method is used to delete the discount code to the current server side quote.
378 | *
379 | * GET PARAMS:
380 | * token - null OR user token obtained from /vsbridge/user/login
381 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
382 | */
383 |
384 | public function delete_coupon(){
385 | $token = $this->getParam('token', true);
386 | $cart_id = $this->getParam('cartId');
387 |
388 | if($this->validateCartId($cart_id, $token)) {
389 | unset($this->session->data['coupon']);
390 | $this->result = true;
391 | }
392 |
393 | $this->sendResponse();
394 | }
395 |
396 | /*
397 | * GET /vsbridge/cart/coupon
398 | * This method is used to get the currently applied coupon code.
399 | *
400 | * GET PARAMS:
401 | * token - null OR user token obtained from /vsbridge/user/login
402 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
403 | */
404 |
405 | public function coupon(){
406 | $token = $this->getParam('token', true);
407 | $cart_id = $this->getParam('cartId');
408 |
409 | if($this->validateCartId($cart_id, $token)) {
410 | if(!empty($this->session->data['coupon'])){
411 | $this->result = $this->session->data['coupon'];
412 | }else{
413 | $this->result = null;
414 | }
415 | }
416 |
417 | $this->sendResponse();
418 | }
419 |
420 |
421 | /*
422 | * GET /vsbridge/cart/totals
423 | * Method called when the config.synchronize_totals=true just after any shopping cart modification.
424 | * It's used to synchronize the Magento / other CMS totals after all promotion rules processed with current Vue Storefront state.
425 | *
426 | * GET PARAMS:
427 | * token - null OR user token obtained from /vsbridge/user/login
428 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
429 | */
430 |
431 | public function totals(){
432 | $token = $this->getParam('token', true);
433 | $cart_id = $this->getParam('cartId');
434 |
435 | if($this->validateCartId($cart_id, $token)) {
436 |
437 | $this->load->model('extension/extension');
438 |
439 | $totals = array();
440 | $taxes = $this->cart->getTaxes();
441 | $total = 0;
442 |
443 | // Because __call can not keep var references so we put them into an array.
444 | $total_data = array(
445 | 'totals' => &$totals,
446 | 'taxes' => &$taxes,
447 | 'total' => &$total
448 | );
449 |
450 | $sort_order = array();
451 |
452 | $results = $this->model_extension_extension->getExtensions('total');
453 |
454 | foreach ($results as $key => $value) {
455 | $sort_order[$key] = $this->config->get($value['code'] . '_sort_order');
456 | }
457 |
458 | array_multisort($sort_order, SORT_ASC, $results);
459 |
460 | foreach ($results as $result) {
461 | if ($this->config->get($result['code'] . '_status')) {
462 | $this->load->model('extension/total/' . $result['code']);
463 |
464 | // We have to put the totals in an array so that they pass by reference.
465 | $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
466 | }
467 | }
468 |
469 | $cart_totals = array();
470 |
471 | foreach ($totals as $key => $value) {
472 | switch($value['code']){
473 | case 'sub_total':
474 | $cart_totals[] = array(
475 | 'code' => 'subtotal',
476 | 'title' => $value['title'],
477 | 'value' => (float)$value['value']
478 | );
479 | break;
480 | case 'shipping':
481 | $cart_totals[] = array(
482 | 'code' => 'shipping',
483 | 'title' => $value['title'],
484 | 'value' => (float)$value['value']
485 | );
486 | break;
487 | case 'tax':
488 | $cart_totals[] = array(
489 | 'code' => 'tax',
490 | 'title' => $value['title'],
491 | 'value' => (float)$value['value']
492 | );
493 | break;
494 | case 'total':
495 | $cart_totals[] = array(
496 | 'code' => 'grand_total',
497 | 'title' => $value['title'],
498 | 'value' => (float)$value['value']
499 | );
500 | break;
501 | case 'coupon':
502 | $cart_totals[] = array(
503 | 'code' => 'discount',
504 | 'title' => $value['title'],
505 | 'value' => (float)$value['value']
506 | );
507 | break;
508 | }
509 | }
510 |
511 | $cart_products = $this->cart->getProducts();
512 |
513 | $cart_items = array();
514 |
515 | /* Currently we don't calculate per-product tax/discount. Instead OpenCart calculates the cart total tax */
516 | foreach($cart_products as $cart_product){
517 | $cart_items[] = array(
518 | 'item_id' => (int)$cart_product['cart_id'],
519 | 'price' => (float)$cart_product['price'],
520 | 'base_price' => (float)$cart_product['price'],
521 | 'qty' => (int)$cart_product['quantity'],
522 | 'row_total' => (float)$cart_product['total'],
523 | 'base_row_total' => (float)$cart_product['total'],
524 | 'row_total_with_discount' => (float)$cart_product['total'],
525 | 'tax_amount' => 0,
526 | 'discount_amount' => 0,
527 | 'base_discount_amount' => 0,
528 | 'discount_percent' => 0,
529 | 'name' => $cart_product['name'],
530 | );
531 | }
532 |
533 | $response = array(
534 | 'items' => $cart_items,
535 | 'total_segments' => $cart_totals
536 | );
537 |
538 | $this->result = $response;
539 |
540 | }
541 |
542 | $this->sendResponse();
543 | }
544 |
545 | /*
546 | * GET /vsbridge/cart/payment-methods
547 | * This method is used as a step in the cart synchronization process to get all the payment methods with actual costs as available inside the backend CMS
548 | *
549 | * GET PARAMS:
550 | * token - null OR user token obtained from /vsbridge/user/login
551 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
552 | *
553 | * Note: this method must be called after a shipment method is set. Otherwise it results in an error.
554 | */
555 |
556 | public function payment_methods(){
557 | $token = $this->getParam('token', true);
558 | $cart_id = $this->getParam('cartId');
559 |
560 | if($this->validateCartId($cart_id, $token)) {
561 | /* We're estimating the payment methods since OpenCart requires address details that aren't provided by VSF at registration */
562 | $this->load->language('checkout/checkout');
563 |
564 | $country_id = $this->config->get('config_country_id');
565 | $zone_id = $this->config->get('config_zone_id');
566 |
567 | $this->load->model('extension/extension');
568 |
569 | $this->load->model('localisation/country');
570 | $country_data = $this->model_localisation_country->getCountry($country_id);
571 |
572 | $this->load->model('localisation/zone');
573 | $zone_data = $this->model_localisation_zone->getZone($zone_id);
574 |
575 | $this->session->data['payment_address'] = array(
576 | 'firstname' => '',
577 | 'lastname' => '',
578 | 'company' => '',
579 | 'address_1' => '',
580 | 'address_2' => '',
581 | 'postcode' => '',
582 | 'city' => '',
583 | 'zone_id' => $zone_id,
584 | 'zone' => (isset($zone_data['name'])) ? $zone_data['name'] : '',
585 | 'zone_code' => (isset($zone_data['code'])) ? $zone_data['code'] : '',
586 | 'country_id' => $country_id,
587 | 'country' => (isset($country_data['name'])) ? $country_data['name'] : '',
588 | 'iso_code_2' => (isset($country_data['iso_code_2'])) ? $country_data['iso_code_2'] : '',
589 | 'iso_code_3' => (isset($country_data['iso_code_3'])) ? $country_data['iso_code_3'] : '',
590 | 'address_format' => (isset($country_data['address_format'])) ? $country_data['address_format'] : '',
591 | );
592 |
593 | if (isset($this->session->data['payment_address'])) {
594 | // Totals
595 | $totals = array();
596 | $taxes = $this->cart->getTaxes();
597 | $total = 0;
598 |
599 | // Because __call can not keep var references so we put them into an array.
600 | $total_data = array(
601 | 'totals' => &$totals,
602 | 'taxes' => &$taxes,
603 | 'total' => &$total
604 | );
605 |
606 | $sort_order = array();
607 |
608 | $results = $this->model_extension_extension->getExtensions('total');
609 |
610 | foreach ($results as $key => $value) {
611 | $sort_order[$key] = $this->config->get($value['code'] . '_sort_order');
612 | }
613 |
614 | array_multisort($sort_order, SORT_ASC, $results);
615 |
616 | foreach ($results as $result) {
617 | if ($this->config->get($result['code'] . '_status')) {
618 | $this->load->model('extension/total/' . $result['code']);
619 |
620 | // We have to put the totals in an array so that they pass by reference.
621 | $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
622 | }
623 | }
624 |
625 | // Payment Methods
626 | $method_data = array();
627 |
628 | $results = $this->model_extension_extension->getExtensions('payment');
629 |
630 | $recurring = $this->cart->hasRecurringProducts();
631 |
632 | foreach ($results as $result) {
633 | if ($this->config->get($result['code'] . '_status')) {
634 | $this->load->model('extension/payment/' . $result['code']);
635 |
636 | $method = $this->{'model_extension_payment_' . $result['code']}->getMethod($this->session->data['payment_address'], $total);
637 |
638 | if ($method) {
639 | if ($recurring) {
640 | if (property_exists($this->{'model_extension_payment_' . $result['code']}, 'recurringPayments') && $this->{'model_extension_payment_' . $result['code']}->recurringPayments()) {
641 | $method_data[$result['code']] = $method;
642 | }
643 | } else {
644 | $method_data[$result['code']] = $method;
645 | }
646 | }
647 | }
648 | }
649 |
650 | $sort_order = array();
651 |
652 | foreach ($method_data as $key => $value) {
653 | $sort_order[$key] = $value['sort_order'];
654 | }
655 |
656 | array_multisort($sort_order, SORT_ASC, $method_data);
657 |
658 | $this->session->data['payment_methods'] = $method_data;
659 | }
660 |
661 | if (empty($this->session->data['payment_methods'])) {
662 | $this->load->language('vsbridge/api');
663 | $this->code = 500;
664 | $this->result = $this->language->get('error_no_payment');
665 | $this->sendResponse();
666 | }
667 |
668 | $adjusted_payment_methods = array();
669 |
670 | foreach($this->session->data['payment_methods'] as $payment_method){
671 | $adjusted_payment_methods[] = array(
672 | 'code' => $payment_method['code'],
673 | 'title' => $payment_method['title']
674 | );
675 | }
676 |
677 | $this->result = $adjusted_payment_methods;
678 | }
679 |
680 | $this->sendResponse();
681 | }
682 |
683 |
684 | /*
685 | * POST /vsbridge/cart/shipping-methods
686 | * This method is used as a step in the cart synchronization process to get all the shipping methods with actual costs as available inside the backend CMS.
687 | *
688 | * GET PARAMS:
689 | * token - null OR user token obtained from /vsbridge/user/login
690 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
691 | *
692 | * REQUEST BODY:
693 | * If the shipping methods are dependent on the full address - probably we need to pass the whole address record with the same format as it's passed to api/order/create or api/user/me.
694 | * The minimum required field is the country_id.
695 | *
696 | * {
697 | * "address":
698 | * {
699 | * "country_id":"PL"
700 | * }
701 | * }
702 | *
703 | * Note: since the minimum passed input is country_id, but OpenCart requires more fields, we will use default values unless provided.
704 | * Update: We decided to ignore country_id (even though it's sent by VSF) and estimate the shipping methods via default config values (since each store is tied to one country).
705 | */
706 |
707 | public function shipping_methods(){
708 | $token = $this->getParam('token', true);
709 | $cart_id = $this->getParam('cartId');
710 |
711 | if($this->validateCartId($cart_id, $token)) {
712 | $this->load->language('api/shipping');
713 |
714 | unset($this->session->data['shipping_methods']);
715 | unset($this->session->data['shipping_method']);
716 |
717 | /* We're estimating the shipping methods since OpenCart requires address details that aren't provided by VSF at registration */
718 |
719 | $shipping_methods = array();
720 |
721 | $country_id = $this->config->get('config_country_id');
722 | $zone_id = $this->config->get('config_zone_id');
723 |
724 | $this->load->model('extension/extension');
725 |
726 | $results = $this->model_extension_extension->getExtensions('shipping');
727 |
728 | $this->load->model('localisation/country');
729 | $country_data = $this->model_localisation_country->getCountry($country_id);
730 |
731 | $this->load->model('localisation/zone');
732 | $zone_data = $this->model_localisation_zone->getZone($zone_id);
733 |
734 | $this->session->data['shipping_address'] = array(
735 | 'firstname' => '',
736 | 'lastname' => '',
737 | 'company' => '',
738 | 'address_1' => '',
739 | 'address_2' => '',
740 | 'postcode' => '',
741 | 'city' => '',
742 | 'zone_id' => $zone_id,
743 | 'zone' => (isset($zone_data['name'])) ? $zone_data['name'] : '',
744 | 'zone_code' => (isset($zone_data['code'])) ? $zone_data['code'] : '',
745 | 'country_id' => $country_id,
746 | 'country' => (isset($country_data['name'])) ? $country_data['name'] : '',
747 | 'iso_code_2' => (isset($country_data['iso_code_2'])) ? $country_data['iso_code_2'] : '',
748 | 'iso_code_3' => (isset($country_data['iso_code_3'])) ? $country_data['iso_code_3'] : '',
749 | 'address_format' => (isset($country_data['address_format'])) ? $country_data['address_format'] : '',
750 | );
751 |
752 | foreach ($results as $result) {
753 | if ($this->config->get($result['code'] . '_status')) {
754 | $this->load->model('extension/shipping/' . $result['code']);
755 |
756 | $quote = $this->{'model_extension_shipping_' . $result['code']}->getQuote($this->session->data['shipping_address']);
757 |
758 | if ($quote) {
759 | // Clear Thinking: Ultimate Restrictions
760 | if (($this->config->get('ultimate_restrictions_status') || $this->config->get('module_ultimate_restrictions_status')) && isset($this->session->data['ultimate_restrictions'])) {
761 | foreach ($quote['quote'] as $index => $restricting_quote) {
762 | foreach ($this->session->data['ultimate_restrictions'] as $extension => $rules) {
763 | if ($extension != $result['code']) continue;
764 | foreach ($rules as $comparison => $values) {
765 | $adjusted_title = explode('(', $restricting_quote['title']);
766 | $adjusted_title = strtolower(html_entity_decode(trim($adjusted_title[0]), ENT_QUOTES, 'UTF-8'));
767 | if (($comparison == 'is' && in_array($adjusted_title, $values)) || ($comparison == 'not' && !in_array($adjusted_title, $values))) {
768 | unset($quote['quote'][$index]);
769 | }
770 | }
771 | }
772 | }
773 | if (empty($quote['quote'])) {
774 | continue;
775 | }
776 | }
777 | // end
778 | $shipping_methods[$result['code']] = array(
779 | 'title' => $quote['title'],
780 | 'quote' => $quote['quote'],
781 | 'sort_order' => $quote['sort_order'],
782 | 'error' => $quote['error']
783 | );
784 | }
785 | }
786 | }
787 |
788 | $sort_order = array();
789 |
790 | foreach ($shipping_methods as $key => $value) {
791 | $sort_order[$key] = $value['sort_order'];
792 | }
793 |
794 | array_multisort($sort_order, SORT_ASC, $shipping_methods);
795 |
796 | if (!$shipping_methods) {
797 | $this->code = 500;
798 | $this->result = $this->language->get('error_no_shipping');
799 | $this->sendResponse();
800 | }
801 |
802 | $this->session->data['shipping_methods'] = $shipping_methods;
803 |
804 | $adjusted_shipping_methods = array();
805 |
806 | foreach($shipping_methods as $smkey => $smvalue){
807 | $adjusted_shipping_methods[] = array(
808 | 'carrier_code' => $smvalue['quote'][$smkey]['code'],
809 | 'method_code' => $smvalue['quote'][$smkey]['code'],
810 | 'carrier_title' => $smvalue['quote'][$smkey]['title'],
811 | 'method_title' => $smvalue['quote'][$smkey]['title'],
812 | 'amount' => (float)$smvalue['quote'][$smkey]['cost'], // Using the shipping price excluding tax since the tax will be applied to the entire order
813 | 'base_amount' => (float)$smvalue['quote'][$smkey]['cost'],
814 | 'available' => $smvalue['error'] ? false : true,
815 | 'error_message' => '',
816 | 'price_excl_tax' => (float)$smvalue['quote'][$smkey]['cost'],
817 | 'price_incl_tax' => ($smvalue['quote'][$smkey]['cost']) ? (float)$this->currency->format($this->tax->calculate($smvalue['quote'][$smkey]['cost'], (int)$smvalue['quote'][$smkey]['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency'], '', false) : 0
818 | );
819 | }
820 |
821 | $this->result = $adjusted_shipping_methods;
822 | }
823 |
824 | $this->sendResponse();
825 | }
826 |
827 | /*
828 | * POST /vsbridge/cart/shipping-information
829 | * This method sets the shipping information on specified quote which is a required step before calling api/cart/collect-totals
830 | *
831 | * GET PARAMS:
832 | * token - null OR user token obtained from /vsbridge/user/login
833 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
834 | *
835 | * REQUEST BODY:
836 | *
837 | * {
838 | * "addressInformation":
839 | * {
840 | * "shipping_address":
841 | * {
842 | * "country_id":"PL"
843 | * },
844 | * "shippingMethodCode":"flatrate",
845 | * "shippingCarrierCode":"flatrate"
846 | * }
847 | * }
848 | *
849 | * TODO: Check the resposne body if there are specific fields VSF needs.
850 | * Note: Changing the array information (i.e. shipping_method_code => shippingMethodCode) due to incorrect API specifications
851 | */
852 |
853 | public function shipping_information(){
854 | $token = $this->getParam('token', true);
855 | $cart_id = $this->getParam('cartId');
856 | $input = $this->getPost();
857 |
858 | if($this->validateCartId($cart_id, $token)) {
859 | // Delete old shipping method so not to cause any issues if there is an error
860 | unset($this->session->data['shipping_method']);
861 |
862 | $this->load->language('api/shipping');
863 |
864 | if ($this->cart->hasShipping()) {
865 | // Shipping Address
866 | if (!isset($this->session->data['shipping_address'])) {
867 | $this->code = 500;
868 | $this->result = $this->language->get('error_address');
869 | }
870 |
871 | // Shipping Method
872 | if (empty($this->session->data['shipping_methods'])) {
873 | $this->code = 500;
874 | $this->result = $this->language->get('error_no_shipping');
875 | } elseif (!isset($input['addressInformation']['shippingMethodCode'])) {
876 | $this->code = 500;
877 | $this->result = $this->language->get('error_method');
878 | } else {
879 | $shipping = explode('.', $input['addressInformation']['shippingMethodCode']);
880 |
881 | if (!isset($shipping[0]) || !isset($shipping[1]) || !isset($this->session->data['shipping_methods'][$shipping[0]]['quote'][$shipping[1]])) {
882 | $this->code = 500;
883 | $this->result = $this->language->get('error_method');
884 | }
885 | }
886 |
887 | if (!$this->result) {
888 | $this->session->data['shipping_method'] = $this->session->data['shipping_methods'][$shipping[0]]['quote'][$shipping[1]];
889 |
890 | $cart_products = $this->cart->getProducts();
891 |
892 | $cart_items = array();
893 |
894 | /* Currently we don't calculate per-product tax/discount. Instead OpenCart calculates the cart total tax */
895 | foreach($cart_products as $cart_product){
896 | $cart_items[] = array(
897 | 'item_id' => (int)$cart_product['cart_id'],
898 | 'price' => (float)$cart_product['price'],
899 | 'base_price' => (float)$cart_product['price'],
900 | 'qty' => (int)$cart_product['quantity'],
901 | 'row_total' => (float)$cart_product['total'],
902 | 'base_row_total' => (float)$cart_product['total'],
903 | 'row_total_with_discount' => (float)$cart_product['total'],
904 | 'tax_amount' => 0,
905 | 'discount_amount' => 0,
906 | 'base_discount_amount' => 0,
907 | 'discount_percent' => 0,
908 | 'name' => $cart_product['name'],
909 | );
910 | }
911 |
912 | $this->result = array(
913 | 'items' => $cart_items,
914 | 'message' => $this->language->get('text_method')
915 | );
916 | }
917 | } else {
918 | unset($this->session->data['shipping_address']);
919 | unset($this->session->data['shipping_method']);
920 | unset($this->session->data['shipping_methods']);
921 | }
922 | }
923 |
924 | $this->sendResponse();
925 | }
926 |
927 | /*
928 | * POST /vsbridge/cart/collect-totals
929 | * This method is called to update the quote totals just after the address information has been changed.
930 | *
931 | * GET PARAMS:
932 | * token - null OR user token obtained from /vsbridge/user/login
933 | * cartId - numeric (integer) value for authorized user cart id or GUID (mixed string) for guest cart ID obtained from api/cart/create
934 | *
935 | * REQUEST BODY:
936 | * {
937 | * "methods": {
938 | * "paymentMethod": {
939 | * "method": "cashondelivery"
940 | * },
941 | * "shippingCarrierCode": "flatrate",
942 | * "shippingMethodCode": "flatrate"
943 | * }
944 | * }
945 | *
946 | * Notes: We haven't found any usage of this method in VSF yet. Will update if necessary since totals are automatically updated via the totals endpoint.
947 | * TODO: Check the resposne body if there are specific fields VSF needs.
948 | */
949 |
950 | public function collect_totals(){
951 | $this->result = array();
952 |
953 | $this->sendResponse();
954 | }
955 | }
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/categories.php:
--------------------------------------------------------------------------------
1 | validateToken($this->getParam('apikey'));
29 |
30 | $store_id = $this->store_id;
31 | $language_id = $this->language_id;
32 |
33 | $this->load->model('vsbridge/api');
34 |
35 | $categories = $this->model_vsbridge_api->getCategories($language_id, $store_id);
36 |
37 | $categories_tree = $this->buildTree($categories);
38 |
39 | $this->mapFields($categories_tree);
40 |
41 | $this->flattenCopy($categories_tree);
42 |
43 | $categories_tree = array_merge($categories_tree, $this->flattenedElements);
44 |
45 | $this->result = $categories_tree;
46 |
47 | $this->sendResponse();
48 | }
49 |
50 | public function buildTree(array &$elements, $parentId = 1) {
51 | $branch = array();
52 |
53 | foreach ($elements as &$element) {
54 | if((int) $element['parent_id'] == 0){
55 | $element['parent_id'] = 1;
56 | }
57 |
58 | if ((int)$element['parent_id'] == $parentId) {
59 | $children = $this->buildTree($elements, (int)$element['category_id']);
60 | if ($children) {
61 | $element['children_data'] = $children;
62 | }
63 |
64 | array_push($branch, $element);
65 | unset($element);
66 | }
67 | }
68 |
69 | return $branch;
70 | }
71 |
72 | public function mapField(array &$array, $oldkey, $newkey, $binary_to_truthy = null){
73 | if(isset($array[$oldkey])){
74 | if(!empty($binary_to_truthy)){
75 | if($array[$oldkey] == 1){
76 | $array[$newkey] = true;
77 | }else{
78 | $array[$newkey] = false;
79 | }
80 | }else{
81 | $array[$newkey] = $array[$oldkey];
82 | }
83 | unset($array[$oldkey]);
84 | }
85 | }
86 |
87 | public function filterFields(array &$array, array $fields){
88 | $new_array = array();
89 |
90 | foreach($fields as $field){
91 | if(isset($array[$field])){
92 | $new_array[$field] = $array[$field];
93 | }
94 | }
95 |
96 | return $new_array;
97 | }
98 |
99 | public function toInt(array &$array, $index){
100 | if(isset($array[$index])){
101 | $array[$index] = (int) $array[$index];
102 | }
103 | }
104 |
105 | // Note: OpenCart does not have slugs for categories. That's why we generate one based on the category name and ID.
106 | // If your implementation uses a custom SEO extension, modify the mapFields function to reflect the correct slug and URL key/path.
107 | public function mapFields(array &$elements, $current_level = 0, $id_path = array(), $name_path = array()){
108 | foreach ($elements as $element_key => &$element) {
109 | if((int) $element['parent_id'] == 1){
110 | $current_level = 0;
111 | $id_path = array();
112 | $name_path = array();
113 | }
114 |
115 | $this->mapField($element, 'category_id', 'id');
116 | $this->mapField($element, 'status', 'is_active', true);
117 | $this->mapField($element, 'date_added', 'created_at');
118 | $this->mapField($element, 'date_modified', 'updated_at');
119 |
120 | $this->toInt($element, 'id');
121 | $this->toInt($element, 'parent_id');
122 |
123 | $this->load->model('vsbridge/api');
124 | $element['product_count'] = (int) $this->model_vsbridge_api->countCategoryProducts($element['id']);
125 |
126 | $name_slug = mb_strtolower(URLify::filter($element['name'], 60, $this->language_code));
127 |
128 | $element['path'] = implode('/', array_merge($id_path, array($element['id'])));
129 |
130 | $element['slug'] = $name_slug.'-'.$element['id'];
131 |
132 | // Change if you use a custom SEO extension
133 | $element['url_key'] = trim($element['slug']);
134 | $element['url_path'] = trim(implode('/', array_merge($name_path, array($name_slug))).'-'.$element['id']);
135 |
136 | // Check for SEO URls via the OpenCart extension [SEO BackPack 2.9.1]
137 | $seo_url_alias = $this->model_vsbridge_api->getSeoUrlAlias('category', $element['id'], $this->language_id);
138 |
139 | if(!empty($seo_url_alias['keyword'])){
140 | $element['url_path'] = trim($seo_url_alias['keyword']);
141 | }
142 |
143 | $element['level'] = $current_level + 1;
144 | $element['position'] = (int) $element_key;
145 |
146 | $element = $this->filterFields($element, array(
147 | 'id',
148 | 'parent_id',
149 | 'name',
150 | 'is_active',
151 | 'position',
152 | 'level',
153 | 'product_count',
154 | 'children_data',
155 | 'children_count',
156 | 'created_at',
157 | 'updated_at',
158 | 'path',
159 | 'slug',
160 | 'url_key',
161 | 'url_path'
162 | ));
163 |
164 | if(!empty($element['children_data'])){
165 | $element['children_count'] = count($element['children_data']);
166 | $this->mapFields($element['children_data'], $current_level+1, array_merge($id_path, array($element['id'])), array_merge($name_path, array($name_slug)));
167 | }else{
168 | $element['children_count'] = 0;
169 | $element['children_data'] = array();
170 | }
171 | }
172 |
173 | unset($element);
174 | }
175 |
176 | public function flattenCopy(array &$elements){
177 | foreach($elements as &$element){
178 | if(!in_array($element['id'], $this->flattenedIds)){
179 |
180 | array_push($this->flattenedElements, $element);
181 | array_push($this->flattenedIds, $element['id']);
182 | }
183 |
184 | if(!empty($element['children_data'])){
185 | $this->flattenCopy($element['children_data']);
186 | }
187 | }
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/order.php:
--------------------------------------------------------------------------------
1 | getPost();
80 |
81 | if(!empty($input['cart_id'])){
82 |
83 | if($this->validateCartId($input['cart_id'], null)) {
84 |
85 | /* Following the logic from catalog/controller/checkout/confirm */
86 |
87 | $this->load->language('vsbridge/api');
88 |
89 | $this->load->model('vsbridge/api');
90 | $this->load->model('localisation/country');
91 | $this->load->model('localisation/zone');
92 |
93 | if ($this->cart->hasShipping()) {
94 | // Validate if shipping address has been set, and copy it to the session info and save if the user is logged in
95 | if (!empty($input['addressInformation']['shippingAddress'])) {
96 |
97 | $shipping_address = array(
98 | 'firstname' => $this->checkInput($input['addressInformation']['shippingAddress'], 'firstname', true, false),
99 | 'lastname' => $this->checkInput($input['addressInformation']['shippingAddress'], 'lastname', true, false),
100 | 'company' => $this->checkInput($input['addressInformation']['shippingAddress'], 'company', false, true, ''),
101 | 'address_1' => implode(' ', $this->checkInput($input['addressInformation']['shippingAddress'], 'street', true, false)),
102 | 'address_2' => '',
103 | 'postcode' => $this->checkInput($input['addressInformation']['shippingAddress'], 'postcode', true, false),
104 | 'city' => $this->checkInput($input['addressInformation']['shippingAddress'], 'city', true, false)
105 | );
106 |
107 | if($zone_id = $this->model_vsbridge_api->getZoneIdFromName($this->checkInput($input['addressInformation']['shippingAddress'], 'region', true, false))){
108 | $shipping_address['zone_id'] = (int) $zone_id;
109 | }else{
110 | $shipping_address['zone_id'] = $this->config->get('config_zone_id');
111 | }
112 |
113 | if($country_id = $this->model_vsbridge_api->getCountryIdFromCode($this->checkInput($input['addressInformation']['shippingAddress'], 'country_id', true, false))){
114 | $shipping_address['country_id'] = (int) $country_id;
115 | }else{
116 | $shipping_address['country_id'] = $this->config->get('config_zone_id');
117 | }
118 |
119 | $country_data = $this->model_localisation_country->getCountry($shipping_address['country_id']);
120 | $zone_data = $this->model_localisation_zone->getZone($shipping_address['zone_id']);
121 |
122 | $shipping_address['zone'] = (isset($zone_data['name'])) ? $zone_data['name'] : '';
123 | $shipping_address['zone_code'] = (isset($zone_data['code'])) ? $zone_data['code'] : '';
124 | $shipping_address['country'] = (isset($country_data['name'])) ? $country_data['name'] : '';
125 | $shipping_address['iso_code_2'] = (isset($country_data['iso_code_2'])) ? $country_data['iso_code_2'] : '';
126 | $shipping_address['iso_code_3'] = (isset($country_data['iso_code_3'])) ? $country_data['iso_code_3'] : '';
127 | $shipping_address['address_format'] = (isset($country_data['address_format'])) ? $country_data['address_format'] : '';
128 |
129 | $this->session->data['shipping_address'] = $shipping_address;
130 |
131 | }else{
132 | $this->error[] = $this->language->get('error_no_shipping_address');
133 | }
134 |
135 | // Validate if shipping method has been set.
136 | if (!isset($this->session->data['shipping_method'])) {
137 | $this->error[] = $this->language->get('error_no_shipping_method');
138 | }
139 | } else {
140 | unset($this->session->data['shipping_address']);
141 | unset($this->session->data['shipping_method']);
142 | unset($this->session->data['shipping_methods']);
143 | }
144 |
145 | // Validate if payment address has been set.
146 | if (!empty($input['addressInformation']['billingAddress'])) {
147 |
148 | $payment_address = array(
149 | 'firstname' => $this->checkInput($input['addressInformation']['billingAddress'], 'firstname', true, false),
150 | 'lastname' => $this->checkInput($input['addressInformation']['billingAddress'], 'lastname', true, false),
151 | 'company' => $this->checkInput($input['addressInformation']['billingAddress'], 'company', false, true, ''),
152 | 'address_1' => implode(' ', $this->checkInput($input['addressInformation']['billingAddress'], 'street', true, false)),
153 | 'address_2' => '',
154 | 'postcode' => $this->checkInput($input['addressInformation']['billingAddress'], 'postcode', true, false),
155 | 'city' => $this->checkInput($input['addressInformation']['billingAddress'], 'city', true, false)
156 | );
157 |
158 | if($zone_id = $this->model_vsbridge_api->getZoneIdFromName($this->checkInput($input['addressInformation']['billingAddress'], 'region', true, false))){
159 | $payment_address['zone_id'] = (int) $zone_id;
160 | }else{
161 | $payment_address['zone_id'] = $this->config->get('config_zone_id');
162 | }
163 |
164 | if($country_id = $this->model_vsbridge_api->getCountryIdFromCode($this->checkInput($input['addressInformation']['billingAddress'], 'country_id', true, false))){
165 | $payment_address['country_id'] = (int) $country_id;
166 | }else{
167 | $payment_address['country_id'] = $this->config->get('config_zone_id');
168 | }
169 |
170 | $country_data = $this->model_localisation_country->getCountry($payment_address['country_id']);
171 | $zone_data = $this->model_localisation_zone->getZone($payment_address['zone_id']);
172 |
173 | $payment_address['zone'] = (isset($zone_data['name'])) ? $zone_data['name'] : '';
174 | $payment_address['zone_code'] = (isset($zone_data['code'])) ? $zone_data['code'] : '';
175 | $payment_address['country'] = (isset($country_data['name'])) ? $country_data['name'] : '';
176 | $payment_address['iso_code_2'] = (isset($country_data['iso_code_2'])) ? $country_data['iso_code_2'] : '';
177 | $payment_address['iso_code_3'] = (isset($country_data['iso_code_3'])) ? $country_data['iso_code_3'] : '';
178 | $payment_address['address_format'] = (isset($country_data['address_format'])) ? $country_data['address_format'] : '';
179 |
180 | $this->session->data['payment_address'] = $payment_address;
181 | }else{
182 | $this->error[] = $this->language->get('error_no_payment_address');
183 | }
184 |
185 | // Validate if payment method has been set.
186 | // Since the payment method is sent via order/create in VSF, we won't check the session.
187 | if (isset($input['addressInformation']['payment_method_code'])) {
188 | $payment_methods = $this->model_extension_extension->getExtensions('payment');
189 |
190 | foreach($payment_methods as $payment_method){
191 | if($payment_method['code'] == $input['addressInformation']['payment_method_code']){
192 | $this->session->data['payment_method']['title'] = '';
193 | $this->session->data['payment_method']['code'] = $input['addressInformation']['payment_method_code'];
194 | $this->load->language('extension/payment/' . $payment_method['code']);
195 | $payment_title = $this->language->get('text_title');
196 |
197 | if($payment_title != 'text_title'){
198 | $this->session->data['payment_method']['title'] = $payment_title;
199 | }
200 | }
201 | }
202 |
203 | if(empty($this->session->data['payment_method']['code'])){
204 | $this->error[] = $this->language->get('error_invalid_payment_code');
205 | }
206 | }else{
207 | $this->error[] = $this->language->get('error_no_payment_method');
208 | }
209 |
210 | // Validate cart has products and has stock.
211 | if ((!$this->cart->hasProducts() && empty($this->session->data['vouchers'])) || (!$this->cart->hasStock() && !$this->config->get('config_stock_checkout'))) {
212 | $this->error[] = $this->language->get('error_cart_product_stock');
213 | }
214 |
215 | // Validate minimum quantity requirements.
216 | $products = $this->cart->getProducts();
217 |
218 | foreach ($products as $product) {
219 | $product_total = 0;
220 |
221 | foreach ($products as $product_2) {
222 | if ($product_2['product_id'] == $product['product_id']) {
223 | $product_total += $product_2['quantity'];
224 | }
225 | }
226 |
227 | if ($product['minimum'] > $product_total) {
228 | $this->error[] = sprintf($this->language->get('error_minimum_product_quantity'), $product['minimum'], $product['model'], $product_total);
229 |
230 | break;
231 | }
232 | }
233 |
234 | if(empty($this->error)){
235 |
236 | $order_data = array();
237 |
238 | $totals = array();
239 | $taxes = $this->cart->getTaxes();
240 | $total = 0;
241 |
242 | // Because __call can not keep var references so we put them into an array.
243 | $total_data = array(
244 | 'totals' => &$totals,
245 | 'taxes' => &$taxes,
246 | 'total' => &$total
247 | );
248 |
249 | $this->load->model('extension/extension');
250 |
251 | $sort_order = array();
252 |
253 | $results = $this->model_extension_extension->getExtensions('total');
254 |
255 | foreach ($results as $key => $value) {
256 | $sort_order[$key] = $this->config->get($value['code'] . '_sort_order');
257 | }
258 |
259 | array_multisort($sort_order, SORT_ASC, $results);
260 |
261 | foreach ($results as $result) {
262 | if ($this->config->get($result['code'] . '_status')) {
263 | $this->load->model('extension/total/' . $result['code']);
264 |
265 | // We have to put the totals in an array so that they pass by reference.
266 | $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
267 | }
268 | }
269 |
270 | $sort_order = array();
271 |
272 | foreach ($totals as $key => $value) {
273 | $sort_order[$key] = $value['sort_order'];
274 | }
275 |
276 | array_multisort($sort_order, SORT_ASC, $totals);
277 |
278 | $order_data['totals'] = $totals;
279 |
280 | $this->load->language('checkout/checkout');
281 |
282 | $order_data['invoice_prefix'] = $this->config->get('config_invoice_prefix');
283 | $order_data['store_id'] = $this->config->get('config_store_id');
284 | $order_data['store_name'] = $this->config->get('config_name');
285 |
286 | if ($order_data['store_id']) {
287 | $order_data['store_url'] = $this->config->get('config_url');
288 | } else {
289 | if ($this->request->server['HTTPS']) {
290 | $order_data['store_url'] = HTTPS_SERVER;
291 | } else {
292 | $order_data['store_url'] = HTTP_SERVER;
293 | }
294 | }
295 |
296 | if ($this->customer->isLogged()) {
297 | $this->load->model('account/customer');
298 |
299 | $customer_info = $this->model_account_customer->getCustomer($this->customer->getId());
300 |
301 | $order_data['customer_id'] = $this->customer->getId();
302 | $order_data['customer_group_id'] = $customer_info['customer_group_id'];
303 | $order_data['firstname'] = $customer_info['firstname'];
304 | $order_data['lastname'] = $customer_info['lastname'];
305 | $order_data['email'] = $customer_info['email'];
306 | $order_data['telephone'] = $customer_info['telephone'];
307 | $order_data['fax'] = $customer_info['fax'];
308 | $order_data['custom_field'] = json_decode($customer_info['custom_field'], true);
309 | } else {
310 | $order_data['customer_id'] = 0;
311 | $order_data['customer_group_id'] = $this->config->get('config_customer_group_id');
312 | $order_data['firstname'] = $this->session->data['shipping_address']['firstname'];
313 | $order_data['lastname'] = $this->session->data['shipping_address']['lastname'];
314 | $order_data['email'] = $this->checkInput($input['addressInformation']['shippingAddress'], 'email', true, false);
315 | $order_data['telephone'] = $this->checkInput($input['addressInformation']['shippingAddress'], 'telephone', false, true);
316 | $order_data['fax'] = '';
317 | $order_data['custom_field'] = array();
318 | }
319 |
320 | $order_data['payment_firstname'] = $this->session->data['payment_address']['firstname'];
321 | $order_data['payment_lastname'] = $this->session->data['payment_address']['lastname'];
322 | $order_data['payment_company'] = $this->session->data['payment_address']['company'];
323 | $order_data['payment_address_1'] = $this->session->data['payment_address']['address_1'];
324 | $order_data['payment_address_2'] = $this->session->data['payment_address']['address_2'];
325 | $order_data['payment_city'] = $this->session->data['payment_address']['city'];
326 | $order_data['payment_postcode'] = $this->session->data['payment_address']['postcode'];
327 | $order_data['payment_zone'] = $this->session->data['payment_address']['zone'];
328 | $order_data['payment_zone_id'] = $this->session->data['payment_address']['zone_id'];
329 | $order_data['payment_country'] = $this->session->data['payment_address']['country'];
330 | $order_data['payment_country_id'] = $this->session->data['payment_address']['country_id'];
331 | $order_data['payment_address_format'] = $this->session->data['payment_address']['address_format'];
332 | $order_data['payment_custom_field'] = (isset($this->session->data['payment_address']['custom_field']) ? $this->session->data['payment_address']['custom_field'] : array());
333 |
334 | if (isset($this->session->data['payment_method']['title'])) {
335 | $order_data['payment_method'] = $this->session->data['payment_method']['title'];
336 | } else {
337 | $order_data['payment_method'] = '';
338 | }
339 |
340 | if (isset($this->session->data['payment_method']['code'])) {
341 | $order_data['payment_code'] = $this->session->data['payment_method']['code'];
342 | } else {
343 | $order_data['payment_code'] = '';
344 | }
345 |
346 | if ($this->cart->hasShipping()) {
347 | $order_data['shipping_firstname'] = $this->session->data['shipping_address']['firstname'];
348 | $order_data['shipping_lastname'] = $this->session->data['shipping_address']['lastname'];
349 | $order_data['shipping_company'] = $this->session->data['shipping_address']['company'];
350 | $order_data['shipping_address_1'] = $this->session->data['shipping_address']['address_1'];
351 | $order_data['shipping_address_2'] = $this->session->data['shipping_address']['address_2'];
352 | $order_data['shipping_city'] = $this->session->data['shipping_address']['city'];
353 | $order_data['shipping_postcode'] = $this->session->data['shipping_address']['postcode'];
354 | $order_data['shipping_zone'] = $this->session->data['shipping_address']['zone'];
355 | $order_data['shipping_zone_id'] = $this->session->data['shipping_address']['zone_id'];
356 | $order_data['shipping_country'] = $this->session->data['shipping_address']['country'];
357 | $order_data['shipping_country_id'] = $this->session->data['shipping_address']['country_id'];
358 | $order_data['shipping_address_format'] = $this->session->data['shipping_address']['address_format'];
359 | $order_data['shipping_custom_field'] = (isset($this->session->data['shipping_address']['custom_field']) ? $this->session->data['shipping_address']['custom_field'] : array());
360 |
361 | if (isset($this->session->data['shipping_method']['title'])) {
362 | $order_data['shipping_method'] = $this->session->data['shipping_method']['title'];
363 | } else {
364 | $order_data['shipping_method'] = '';
365 | }
366 |
367 | if (isset($this->session->data['shipping_method']['code'])) {
368 | $order_data['shipping_code'] = $this->session->data['shipping_method']['code'];
369 | } else {
370 | $order_data['shipping_code'] = '';
371 | }
372 | } else {
373 | $order_data['shipping_firstname'] = '';
374 | $order_data['shipping_lastname'] = '';
375 | $order_data['shipping_company'] = '';
376 | $order_data['shipping_address_1'] = '';
377 | $order_data['shipping_address_2'] = '';
378 | $order_data['shipping_city'] = '';
379 | $order_data['shipping_postcode'] = '';
380 | $order_data['shipping_zone'] = '';
381 | $order_data['shipping_zone_id'] = '';
382 | $order_data['shipping_country'] = '';
383 | $order_data['shipping_country_id'] = '';
384 | $order_data['shipping_address_format'] = '';
385 | $order_data['shipping_custom_field'] = array();
386 | $order_data['shipping_method'] = '';
387 | $order_data['shipping_code'] = '';
388 | }
389 |
390 | $order_data['products'] = array();
391 |
392 | foreach ($this->cart->getProducts() as $product) {
393 | $option_data = array();
394 |
395 | foreach ($product['option'] as $option) {
396 | $option_data[] = array(
397 | 'product_option_id' => $option['product_option_id'],
398 | 'product_option_value_id' => $option['product_option_value_id'],
399 | 'option_id' => $option['option_id'],
400 | 'option_value_id' => $option['option_value_id'],
401 | 'name' => $option['name'],
402 | 'value' => $option['value'],
403 | 'type' => $option['type']
404 | );
405 | }
406 |
407 | $order_data['products'][] = array(
408 | 'product_id' => $product['product_id'],
409 | 'name' => $product['name'],
410 | 'base_price' => $product['base_price'],
411 | 'cost' => $product['cost'],
412 | 'supplier_id' => $product['supplier_id'],
413 | 'model' => $product['model'],
414 | 'option' => $option_data,
415 | 'download' => $product['download'],
416 | 'quantity' => $product['quantity'],
417 | 'subtract' => $product['subtract'],
418 | 'price' => $product['price'],
419 | 'total' => $product['total'],
420 | 'tax' => $this->tax->getTax($product['price'], $product['tax_class_id']),
421 | 'reward' => $product['reward']
422 | );
423 | }
424 |
425 | // Gift Voucher
426 | $order_data['vouchers'] = array();
427 |
428 | if (!empty($this->session->data['vouchers'])) {
429 | foreach ($this->session->data['vouchers'] as $voucher) {
430 | $order_data['vouchers'][] = array(
431 | 'description' => $voucher['description'],
432 | 'code' => token(10),
433 | 'to_name' => $voucher['to_name'],
434 | 'to_email' => $voucher['to_email'],
435 | 'from_name' => $voucher['from_name'],
436 | 'from_email' => $voucher['from_email'],
437 | 'voucher_theme_id' => $voucher['voucher_theme_id'],
438 | 'message' => $voucher['message'],
439 | 'amount' => $voucher['amount']
440 | );
441 | }
442 | }
443 |
444 | $vat_id = $this->checkInput($input['addressInformation']['billingAddress'], 'vat_id', false, true, null);
445 |
446 | $order_data['comment'] = $vat_id ? 'VAT ID: '.$vat_id : ''; // not implemented yet - we store the VAT ID here if provided
447 | $order_data['total'] = $total_data['total'];
448 |
449 | if (isset($this->request->cookie['tracking'])) {
450 | $order_data['tracking'] = $this->request->cookie['tracking'];
451 |
452 | $subtotal = $this->cart->getSubTotal();
453 |
454 | // Affiliate
455 | $this->load->model('affiliate/affiliate');
456 |
457 | $affiliate_info = $this->model_affiliate_affiliate->getAffiliateByCode($this->request->cookie['tracking']);
458 |
459 | if ($affiliate_info) {
460 | $order_data['affiliate_id'] = $affiliate_info['affiliate_id'];
461 | $order_data['commission'] = ($subtotal / 100) * $affiliate_info['commission'];
462 | } else {
463 | $order_data['affiliate_id'] = 0;
464 | $order_data['commission'] = 0;
465 | }
466 |
467 | // Marketing
468 | $this->load->model('checkout/marketing');
469 |
470 | $marketing_info = $this->model_checkout_marketing->getMarketingByCode($this->request->cookie['tracking']);
471 |
472 | if ($marketing_info) {
473 | $order_data['marketing_id'] = $marketing_info['marketing_id'];
474 | } else {
475 | $order_data['marketing_id'] = 0;
476 | }
477 | } else {
478 | $order_data['affiliate_id'] = 0;
479 | $order_data['commission'] = 0;
480 | $order_data['marketing_id'] = 0;
481 | $order_data['tracking'] = '';
482 | }
483 |
484 | $order_data['language_id'] = $this->config->get('config_language_id');
485 | $order_data['currency_id'] = $this->currency->getId($this->session->data['currency']);
486 | $order_data['currency_code'] = $this->session->data['currency'];
487 | $order_data['currency_value'] = $this->currency->getValue($this->session->data['currency']);
488 | $order_data['ip'] = $this->request->server['REMOTE_ADDR'];
489 |
490 | // Fixing some PHP notice errors due to OCmods
491 | $order_data['payment_cost'] = '';
492 | $order_data['shipping_cost'] = '';
493 | $order_data['extra_cost'] = '';
494 |
495 | if (!empty($this->request->server['HTTP_X_FORWARDED_FOR'])) {
496 | $order_data['forwarded_ip'] = $this->request->server['HTTP_X_FORWARDED_FOR'];
497 | } elseif (!empty($this->request->server['HTTP_CLIENT_IP'])) {
498 | $order_data['forwarded_ip'] = $this->request->server['HTTP_CLIENT_IP'];
499 | } else {
500 | $order_data['forwarded_ip'] = '';
501 | }
502 |
503 | if (isset($this->request->server['HTTP_USER_AGENT'])) {
504 | $order_data['user_agent'] = $this->request->server['HTTP_USER_AGENT'];
505 | } else {
506 | $order_data['user_agent'] = '';
507 | }
508 |
509 | if (isset($this->request->server['HTTP_ACCEPT_LANGUAGE'])) {
510 | $order_data['accept_language'] = $this->request->server['HTTP_ACCEPT_LANGUAGE'];
511 | } else {
512 | $order_data['accept_language'] = '';
513 | }
514 |
515 | $this->load->model('checkout/order');
516 |
517 | $order_id = $this->model_checkout_order->addOrder($order_data);
518 |
519 | $this->session->data['order_id'] = $order_id;
520 |
521 | $order_status_id = $this->config->get('config_order_status_id');
522 |
523 | $this->model_checkout_order->addOrderHistory($order_id, $order_status_id);
524 |
525 | // clear cart since the order has already been successfully stored.
526 | $this->cart->clear();
527 |
528 | // clear session data
529 | unset($this->session->data['shipping_method']);
530 | unset($this->session->data['shipping_methods']);
531 | unset($this->session->data['payment_method']);
532 | unset($this->session->data['payment_methods']);
533 | unset($this->session->data['guest']);
534 | unset($this->session->data['comment']);
535 | unset($this->session->data['order_id']);
536 | unset($this->session->data['coupon']);
537 | unset($this->session->data['reward']);
538 | unset($this->session->data['voucher']);
539 | unset($this->session->data['vouchers']);
540 | unset($this->session->data['totals']);
541 |
542 | $this->result = array(
543 | 'order_id' => $order_id
544 | );
545 |
546 | }else{
547 | $this->code = 500;
548 | $this->result = implode("\n", $this->error);
549 | }
550 |
551 | }
552 |
553 | }else{
554 | $this->load->language('vsbridge/api');
555 | $this->code = 500;
556 | $this->result = $this->language->get('error_missing_input').'cart_id';
557 | }
558 |
559 | $this->sendResponse();
560 | }
561 | }
562 |
563 |
--------------------------------------------------------------------------------
/src/catalog/controller/vsbridge/product.php:
--------------------------------------------------------------------------------
1 | list();
28 | break;
29 |
30 | case 'render-list':
31 | $this->renderList();
32 | break;
33 | }
34 |
35 | die();
36 | }
37 |
38 | /*
39 | * RESPONSE:
40 | * {
41 | "code": 200,
42 | "result": {
43 | "items": [
44 | {
45 | "id": 1866,
46 | "sku": "WP07",
47 | "name": "Aeon Capri",
48 | "price": 0,
49 | "status": 1,
50 | "visibility": 4,
51 | "type_id": "configurable",
52 | "created_at": "2017-11-06 12:17:26",
53 | "updated_at": "2017-11-06 12:17:26",
54 | "product_links": [],
55 | "tier_prices": [],
56 | "custom_attributes": [
57 | {
58 | "attribute_code": "description",
59 | "value": "
Reach for the stars and beyond in these Aeon Capri pant. With a soft, comfortable feel and moisture wicking fabric, these duo-tone leggings are easy to wear -- and wear attractively.
\n
• Black capris with teal accents. • Thick, 3\" flattering waistband. • Media pocket on inner waistband. • Dry wick finish for ultimate comfort and dryness.