Printful Integration for WooCommerce - Version 1.1

Version Description

Added tax rate calculation

Download this release

Release Info

Developer printful
Plugin Icon 128x128 Printful Integration for WooCommerce
Version 1.1
Comparing to
See all releases

Code changes from version 1.0.2 to 1.1

PrintfulClient.php DELETED
@@ -1,177 +0,0 @@
1
- <?php
2
- /**
3
- * This class helps to use the Printful API
4
- *
5
- * Requires PHP version 5, JSON and CURL extensions
6
- *
7
- * @package Printful
8
- * @version 1.0
9
- * @copyright 2014 Idea Bits LLC
10
- */
11
-
12
- class PrintfulClient {
13
- private $key = false;
14
- private $lastResponseRaw;
15
- private $lastResponse;
16
-
17
- public $disableSSL = false;
18
-
19
- const API_URL = 'https://api.theprintful.com/';
20
- const USER_AGENT = 'Printful API WooCommerce Library 1.0.2';
21
-
22
- /**
23
- * @param string $key Printful Store API key
24
- * @throws PrintfulException if the library failed to initialize
25
- */
26
- public function __construct($key = ''){
27
- $key = (string)$key;
28
-
29
- if(!function_exists('json_decode') || !function_exists('json_encode')){
30
- throw new PrintfulException('PHP JSON extension is required for the Printful API library to work!');
31
- }
32
- if(!function_exists('curl_init') ){
33
- throw new PrintfulException('PHP CURL extension is required for the Printful API library to work!');
34
- }
35
- if(strlen($key) < 32){
36
- throw new PrintfulException('Missing or invalid Printful store key!');
37
- }
38
- $this->key = $key;
39
- }
40
-
41
- /**
42
- * Returns total available item count from the last request if it supports paging (e.g order list) or null otherwise.
43
- *
44
- * @return int|null Item count
45
- */
46
- public function getItemCount(){
47
- return isset($this->lastResponse['paging']['total']) ? $this->lastResponse['paging']['total'] : null;
48
- }
49
-
50
- /**
51
- * Perform a GET request to the API
52
- * @param string $path Request path (e.g. 'orders' or 'orders/123')
53
- * @param array $params Additional GET parameters as an associative array
54
- * @return mixed API response
55
- * @throws PrintfulApiException if the API call status code is not in the 2xx range
56
- * @throws PrintfulException if the API call has failed or the response is invalid
57
- */
58
- public function get($path, $params = array()){
59
- return $this->request('GET', $path, $params);
60
- }
61
-
62
- /**
63
- * Perform a DELETE request to the API
64
- * @param string $path Request path (e.g. 'orders' or 'orders/123')
65
- * @param array $params Additional GET parameters as an associative array
66
- * @return mixed API response
67
- * @throws PrintfulApiException if the API call status code is not in the 2xx range
68
- * @throws PrintfulException if the API call has failed or the response is invalid
69
- */
70
- public function delete($path, $params = array()){
71
- return $this->request('DELETE', $path, $params);
72
- }
73
-
74
- /**
75
- * Perform a POST request to the API
76
- * @param string $path Request path (e.g. 'orders' or 'orders/123')
77
- * @param array $data Request body data as an associative array
78
- * @param array $params Additional GET parameters as an associative array
79
- * @return mixed API response
80
- * @throws PrintfulApiException if the API call status code is not in the 2xx range
81
- * @throws PrintfulException if the API call has failed or the response is invalid
82
- */
83
- public function post($path, $data = array(), $params = array()){
84
- return $this->request('POST', $path, $params, $data);
85
- }
86
- /**
87
- * Perform a PUT request to the API
88
- * @param string $path Request path (e.g. 'orders' or 'orders/123')
89
- * @param array $data Request body data as an associative array
90
- * @param array $params Additional GET parameters as an associative array
91
- * @return mixed API response
92
- * @throws PrintfulApiException if the API call status code is not in the 2xx range
93
- * @throws PrintfulException if the API call has failed or the response is invalid
94
- */
95
- public function put($path, $data = array(), $params = array()){
96
- return $this->request('PUT', $path, $params, $data);
97
- }
98
-
99
- /**
100
- * Return raw response data from the last request
101
- * @return string|null Response data
102
- */
103
- public function getLastResponseRaw(){
104
- return $this->lastResponseRaw;
105
- }
106
- /**
107
- * Return decoded response data from the last request
108
- * @return array|null Response data
109
- */
110
- public function getLastResponse(){
111
- return $this->lastResponse;
112
- }
113
-
114
- /**
115
- * Internal request implementation
116
- */
117
- private function request($method, $path, array $params = array(), $data = null){
118
-
119
- $this->lastResponseRaw = null;
120
- $this->lastResponse = null;
121
-
122
- $url = trim($path,'/');
123
-
124
- if(!empty($params)){
125
- $url .= '?'.http_build_query($params);
126
- }
127
-
128
- $version = curl_version();
129
- $baseUrl = self::API_URL;
130
- if($this->disableSSL || !($version['features'] & CURL_VERSION_SSL)){ //Fallback to HTTP
131
- $baseUrl = str_replace('https://', 'http://', $baseUrl);
132
- }
133
-
134
- $curl = curl_init($baseUrl.$url);
135
-
136
- curl_setopt($curl, CURLOPT_USERPWD, $this->key);
137
- curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
138
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
139
-
140
- curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
141
- curl_setopt($curl, CURLOPT_TIMEOUT, 20);
142
-
143
- curl_setopt($curl, CURLOPT_USERAGENT, self::USER_AGENT);
144
-
145
- if($data !==null){
146
- curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
147
- }
148
-
149
- $this->lastResponseRaw = curl_exec($curl);
150
-
151
- $errno = curl_errno($curl);
152
- $error = curl_error($curl);
153
- curl_close($curl);
154
-
155
- if ($errno) throw new PrintfulException('CURL: '.$error, $errno);
156
-
157
- $this->lastResponse = $response = json_decode($this->lastResponseRaw, true);
158
-
159
- if(!isset($response['code'], $response['result'])){
160
- throw new PrintfulException('Invalid API response');
161
- }
162
- $status = (int)$response['code'];
163
- if($status < 200 || $status >= 300){
164
- throw new PrintfulApiException((string)$response['result'], $status);
165
- }
166
- return $response['result'];
167
- }
168
- }
169
-
170
- /**
171
- * Class PrintfulException Generic Printful exception
172
- */
173
- class PrintfulException extends Exception {}
174
- /**
175
- * Class PrintfulException Printful exception returned from the API
176
- */
177
- class PrintfulApiException extends PrintfulException {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-printful-api-resource.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_API_Resource extends WC_API_Resource {
5
+
6
+ /** @var string $base the route base */
7
+ protected $base = '/printful';
8
+
9
+ public function register_routes( $routes ) {
10
+
11
+ $routes[ $this->base.'/version' ] = array(
12
+ array( array( $this, 'get_status' ), WC_API_Server::READABLE | WC_API_Server::HIDDEN_ENDPOINT),
13
+ );
14
+ return $routes;
15
+ }
16
+
17
+ /**
18
+ * Allow remotely get plugin version for debug purposes
19
+ */
20
+ public function get_status(){
21
+ $error = false;
22
+ try {
23
+ $client = Printful_Integration::instance()->get_client();
24
+ $storeData = $client->get('store');
25
+ }
26
+ catch(Exception $e){
27
+ $error = $e->getMessage();
28
+ }
29
+ return array(
30
+ 'version' => Printful_Base::VERSION,
31
+ 'api_key' => !empty(Printful_Integration::instance()->settings['printful_key']),
32
+ 'store_id' => !empty($storeData['id']) ? $storeData['id'] : false,
33
+ 'error' => $error,
34
+ );
35
+ }
36
+
37
+ }
includes/class-printful-client.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Printful API client
4
+ */
5
+
6
+ class Printful_Client {
7
+ private $key = false;
8
+ private $lastResponseRaw;
9
+ private $lastResponse;
10
+ private $userAgent = 'Printful WooCommerce Plugin';
11
+ private $apiUrl = 'https://api.theprintful.com/';
12
+
13
+ /**
14
+ * @param string $key Printful Store API key
15
+ * @param string $disable_ssl Force HTTP instead of HTTPS for API requests
16
+ * @throws PrintfulException if the library failed to initialize
17
+ */
18
+ public function __construct($key = '', $disable_ssl = false){
19
+ $key = (string)$key;
20
+
21
+ $this->userAgent .= ' '.Printful_Base::VERSION.' (WP '. get_bloginfo( 'version' ) . ' + WC ' . WC()->version.')';
22
+
23
+ if(!function_exists('json_decode') || !function_exists('json_encode')){
24
+ throw new PrintfulException('PHP JSON extension is required for the Printful API library to work!');
25
+ }
26
+ if(!function_exists('curl_init') ){
27
+ throw new PrintfulException('PHP CURL extension is required for the Printful API library to work!');
28
+ }
29
+ if(strlen($key) < 32){
30
+ throw new PrintfulException('Missing or invalid Printful store key!');
31
+ }
32
+ $this->key = $key;
33
+
34
+ if($disable_ssl)
35
+ {
36
+ $this->apiUrl = str_replace('https://','http://', $this->apiUrl);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Returns total available item count from the last request if it supports paging (e.g order list) or null otherwise.
42
+ *
43
+ * @return int|null Item count
44
+ */
45
+ public function getItemCount(){
46
+ return isset($this->lastResponse['paging']['total']) ? $this->lastResponse['paging']['total'] : null;
47
+ }
48
+
49
+ /**
50
+ * Perform a GET request to the API
51
+ * @param string $path Request path (e.g. 'orders' or 'orders/123')
52
+ * @param array $params Additional GET parameters as an associative array
53
+ * @return mixed API response
54
+ * @throws PrintfulApiException if the API call status code is not in the 2xx range
55
+ * @throws PrintfulException if the API call has failed or the response is invalid
56
+ */
57
+ public function get($path, $params = array()){
58
+ return $this->request('GET', $path, $params);
59
+ }
60
+
61
+ /**
62
+ * Perform a DELETE request to the API
63
+ * @param string $path Request path (e.g. 'orders' or 'orders/123')
64
+ * @param array $params Additional GET parameters as an associative array
65
+ * @return mixed API response
66
+ * @throws PrintfulApiException if the API call status code is not in the 2xx range
67
+ * @throws PrintfulException if the API call has failed or the response is invalid
68
+ */
69
+ public function delete($path, $params = array()){
70
+ return $this->request('DELETE', $path, $params);
71
+ }
72
+
73
+ /**
74
+ * Perform a POST request to the API
75
+ * @param string $path Request path (e.g. 'orders' or 'orders/123')
76
+ * @param array $data Request body data as an associative array
77
+ * @param array $params Additional GET parameters as an associative array
78
+ * @return mixed API response
79
+ * @throws PrintfulApiException if the API call status code is not in the 2xx range
80
+ * @throws PrintfulException if the API call has failed or the response is invalid
81
+ */
82
+ public function post($path, $data = array(), $params = array()){
83
+ return $this->request('POST', $path, $params, $data);
84
+ }
85
+ /**
86
+ * Perform a PUT request to the API
87
+ * @param string $path Request path (e.g. 'orders' or 'orders/123')
88
+ * @param array $data Request body data as an associative array
89
+ * @param array $params Additional GET parameters as an associative array
90
+ * @return mixed API response
91
+ * @throws PrintfulApiException if the API call status code is not in the 2xx range
92
+ * @throws PrintfulException if the API call has failed or the response is invalid
93
+ */
94
+ public function put($path, $data = array(), $params = array()){
95
+ return $this->request('PUT', $path, $params, $data);
96
+ }
97
+
98
+ /**
99
+ * Return raw response data from the last request
100
+ * @return string|null Response data
101
+ */
102
+ public function getLastResponseRaw(){
103
+ return $this->lastResponseRaw;
104
+ }
105
+ /**
106
+ * Return decoded response data from the last request
107
+ * @return array|null Response data
108
+ */
109
+ public function getLastResponse(){
110
+ return $this->lastResponse;
111
+ }
112
+
113
+ /**
114
+ * Internal request implementation
115
+ */
116
+ private function request($method, $path, array $params = array(), $data = null){
117
+
118
+ $this->lastResponseRaw = null;
119
+ $this->lastResponse = null;
120
+
121
+ $url = trim($path,'/');
122
+
123
+ if(!empty($params)){
124
+ $url .= '?'.http_build_query($params);
125
+ }
126
+
127
+ $result = wp_remote_get($this->apiUrl . $url, array(
128
+ 'timeout' => 10,
129
+ 'user-agent' => $this->userAgent,
130
+ 'method' => $method,
131
+ 'headers' => array(
132
+ 'Authorization' => 'Basic ' . base64_encode($this->key)
133
+ ),
134
+ 'body' => $data !== null ? json_encode($data) : null
135
+ ));
136
+
137
+ if (is_wp_error($result))
138
+ {
139
+ throw new PrintfulException("API request failed - ". $result->get_error_message());
140
+ }
141
+ $this->lastResponseRaw = $result['body'];
142
+ $this->lastResponse = $response = json_decode($result['body'], true);
143
+
144
+ if(!isset($response['code'], $response['result'])){
145
+ throw new PrintfulException('Invalid API response');
146
+ }
147
+ $status = (int)$response['code'];
148
+ if($status < 200 || $status >= 300){
149
+ throw new PrintfulApiException((string)$response['result'], $status);
150
+ }
151
+ return $response['result'];
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Class PrintfulException Generic Printful exception
157
+ */
158
+ class PrintfulException extends Exception {}
159
+ /**
160
+ * Class PrintfulException Printful exception returned from the API
161
+ */
162
+ class PrintfulApiException extends PrintfulException {}
includes/class-printful-integration.php ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Integration extends WC_Integration
5
+ {
6
+
7
+ public static $_instance;
8
+
9
+ public static function instance() {
10
+ if ( is_null( self::$_instance ) )
11
+ self::$_instance = new self();
12
+ return self::$_instance;
13
+ }
14
+
15
+ public function __construct()
16
+ {
17
+ $this->id = 'printful';
18
+ $this->method_title = 'Printful Integration';
19
+ $this->method_description = 'Enable integration with Printful fulfillment service';
20
+
21
+ add_action('woocommerce_update_options_integration_' . $this->id, array($this, 'process_admin_options'));
22
+
23
+ $this->init_form_fields();
24
+ $this->init_settings();
25
+
26
+ if ($this->get_option('calculate_tax') == 'yes')
27
+ {
28
+ //Update tax options if taxes are enabled
29
+ if (get_option('woocommerce_calc_taxes') != 'yes')
30
+ {
31
+ update_option('woocommerce_calc_taxes', 'yes');
32
+ }
33
+ if (get_option('woocommerce_tax_based_on') != 'shipping')
34
+ {
35
+ update_option('woocommerce_tax_based_on', 'shipping');
36
+ }
37
+
38
+ //Show warning in the tax settings section
39
+ add_action('woocommerce_settings_tax_options', array($this, 'show_tax_warning'));
40
+
41
+ //Override tax rates calculated by Woocommerce
42
+ add_filter('woocommerce_matched_tax_rates', array($this, 'calculate_tax'), 10, 6);
43
+ }
44
+
45
+ self::$_instance = $this;
46
+ }
47
+
48
+ public function get_client()
49
+ {
50
+ require_once 'class-printful-client.php';
51
+ $client = new Printful_Client($this->get_option('printful_key'), $this->get_option('disable_ssl') == 'yes');
52
+ return $client;
53
+ }
54
+
55
+ public function init_settings()
56
+ {
57
+ parent::init_settings();
58
+
59
+ //Copy settings from old plugin settings location if upgraded from plugin version 1.0.2
60
+ if ($this->get_option('printful_key') === false)
61
+ {
62
+ $oldsettings = get_option( $this->plugin_id . 'printful_shipping_settings', null );
63
+ $this->settings['printful_key'] = '';
64
+
65
+ if(!empty($oldsettings['printful_key']))
66
+ {
67
+ $this->settings['printful_key'] = $oldsettings['printful_key'];
68
+ }
69
+ if(!empty($oldsettings['disable_ssl']))
70
+ {
71
+ $this->settings['disable_ssl'] = $oldsettings['disable_ssl'];
72
+ }
73
+
74
+ update_option($this->plugin_id . 'printful_settings', $this->settings);
75
+ }
76
+ }
77
+
78
+ public function init_form_fields()
79
+ {
80
+ $this->form_fields = array(
81
+ 'printful_key' => array(
82
+ 'title' => 'Printful store API key',
83
+ 'type' => 'text',
84
+ 'desc_tip' => true,
85
+ 'description'=> 'Your store\'s Printful API key. Create it in the Prinful dashboard',
86
+ 'default' => false,
87
+ ),
88
+ 'calculate_tax' => array(
89
+ 'title' => 'Calculate sales tax',
90
+ 'type' => 'checkbox',
91
+ 'label' => 'Calculate sales tax for locations where it is required for Printful orders',
92
+ 'default' => 'no'
93
+ ),
94
+ 'disable_ssl' => array(
95
+ 'title' => 'Disable SSL',
96
+ 'type' => 'checkbox',
97
+ 'label' => 'Use HTTP instead of HTTPS to connect to the Printful API (may be required if the plugin does not work for some hosting configurations)',
98
+ 'default' => 'no'
99
+ ),
100
+ );
101
+ }
102
+
103
+ public function generate_settings_html($form_fields = false)
104
+ {
105
+ parent::generate_settings_html($form_fields);
106
+
107
+ ?>
108
+ <tr valign="top">
109
+ <th scope="row" class="titledesc">
110
+ <label >Calculate shipping rates</label>
111
+ </th>
112
+ <td class="forminp">
113
+ Go to <b><a href="<?php echo admin_url('admin.php?page=wc-settings&tab=shipping&section=printful_shipping') ?>">Shipping → Printful Shipping</a></b> to enable Printful shipping rate calculation
114
+ </td>
115
+ </tr>
116
+ <?php
117
+ }
118
+
119
+ public function calculate_tax($matched_tax_rates, $country, $state, $postcode, $city, $tax_class)
120
+ {
121
+ $countries = $this->get_tax_countries();
122
+ if(isset($countries[$country][$state]))
123
+ {
124
+ $rate = false;
125
+ try {
126
+ $client = $this->get_client();
127
+ $response = $client->post('tax/rates', array(
128
+ 'recipient' => array(
129
+ 'country_code' => $country,
130
+ 'state_code' => $state,
131
+ 'city' => $city,
132
+ 'zip' => $postcode,
133
+ )
134
+ ));
135
+ }
136
+ catch(Exception $e)
137
+ {}
138
+
139
+ if(isset($response['rate']))
140
+ {
141
+ $rate = $response['rate'];
142
+ }
143
+
144
+ if ($rate)
145
+ {
146
+ $id = $this->get_printful_rate_id($country, $state);
147
+ return array(
148
+ $id => array(
149
+ 'rate' => $rate * 100,
150
+ 'label' => 'Sales Tax',
151
+ 'shipping' => 'no',
152
+ 'compound' => 'no',
153
+ )
154
+ );
155
+ }
156
+ }
157
+ //Return no taxes
158
+ return array();
159
+ }
160
+
161
+ /**
162
+ * Gets list of countries and states where Printful needs to calculate sales tax
163
+ */
164
+ private function get_tax_countries()
165
+ {
166
+ $countries = get_transient('printful_tax_countries');
167
+ if (!$countries)
168
+ {
169
+ $countries = array();
170
+ try {
171
+ $client = $this->get_client();
172
+ $list = $client->get('tax/countries');
173
+
174
+ foreach($list as $country)
175
+ {
176
+ $list[$country['country']] = array();
177
+ foreach($country['states'] as $state){
178
+ $countries[$country['code']][$state['code']] = 1;
179
+ }
180
+ }
181
+ if(!empty($countries))
182
+ {
183
+ set_transient('printful_tax_countries', $countries, 86400);
184
+ }
185
+ }
186
+ catch(Exception $e)
187
+ {
188
+ //Default to CA if can't get the actual state list
189
+ return array('US' => array('CA' => 1));
190
+ }
191
+ }
192
+ return $countries;
193
+ }
194
+
195
+ /**
196
+ * Creates dummy tax rate ID to display Printful tax rates in the cart summary.
197
+ */
198
+ private function get_printful_rate_id($cc, $state)
199
+ {
200
+ global $wpdb;
201
+
202
+ $states = WC()->countries->get_states($cc);
203
+ $tax_title = (isset($states[$state]) ? $states[$state] .' ': '' ). 'Sales Tax';
204
+ $id = $wpdb->get_var(
205
+ $wpdb->prepare("SELECT tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_class='printful'
206
+ and tax_rate_country = %s AND tax_rate_state = %s LIMIT 1",
207
+ '_'.$cc,
208
+ $state
209
+ ));
210
+ if(empty($id))
211
+ {
212
+ $wpdb->insert(
213
+ $wpdb->prefix . "woocommerce_tax_rates",
214
+ array(
215
+ 'tax_rate_country' => '_'.$cc,
216
+ 'tax_rate_state' => $state,
217
+ 'tax_rate' => 0,
218
+ 'tax_rate_name' => $tax_title,
219
+ 'tax_rate_priority' => 1,
220
+ 'tax_rate_compound' => 0,
221
+ 'tax_rate_shipping' => 0,
222
+ 'tax_rate_class' => 'printful'
223
+ )
224
+ );
225
+ $id = $wpdb->insert_id;
226
+ }
227
+ return $id;
228
+ }
229
+
230
+ public function show_tax_warning(){
231
+ ?>
232
+ <div class="error below-h2">
233
+ <p>
234
+ Warning: Tax rates are overriden by Printful Integration plugin. Go to
235
+ <a href="<?php echo admin_url('admin.php?page=wc-settings&tab=integration&section=printful') ?>">Printful Integration settings</a>
236
+ to disable automatic tax calculation if you want to use your own settings.
237
+ </p>
238
+ </div>
239
+ <?php
240
+ }
241
+
242
+ }
includes/class-printful-shipping.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Shipping extends WC_Shipping_Method
5
+ {
6
+ public $show_warnings = false;
7
+ public $calculate_tax = false;
8
+ private $last_error = false;
9
+
10
+ public function __construct()
11
+ {
12
+ $this->id = 'printful_shipping';
13
+ $this->method_title = $this->title = 'Printful Shipping';
14
+ $this->method_description = 'Calculate live shipping rates based on actual Printful shipping costs.';
15
+
16
+ $this->init_form_fields();
17
+ $this->init_settings();
18
+
19
+ add_action('woocommerce_update_options_shipping_' . $this->id, array(&$this, 'process_admin_options'));
20
+
21
+ $this->enabled = $this->get_option('enabled');
22
+ $this->show_warnings = $this->get_option('show_warnings') == 'yes';
23
+
24
+ $this->type = 'order';
25
+ }
26
+
27
+ public function generate_settings_html($form_fields = false)
28
+ {
29
+ if (empty(Printful_Integration::instance()->settings['printful_key']))
30
+ {
31
+ ?>
32
+ <tr><td colspan="2">
33
+ <div class="error below-h2" style="margin:0">
34
+ <p>
35
+ Please add Printful API key to the
36
+ <a href="<?php echo admin_url('admin.php?page=wc-settings&tab=integration&section=printful') ?>">Printful Integration settings section</a>
37
+ to enable rate calculation.
38
+ </p>
39
+ </div>
40
+ </td></tr>
41
+ <?php
42
+ }
43
+ parent::generate_settings_html($form_fields);
44
+
45
+ }
46
+
47
+ public function init_form_fields()
48
+ {
49
+ $this->form_fields = array(
50
+ 'enabled' => array(
51
+ 'title' => __( 'Enable/Disable', 'woocommerce' ),
52
+ 'type' => 'checkbox',
53
+ 'label' => __( 'Enable this shipping method', 'woocommerce' ),
54
+ 'default' => 'no'
55
+ ),
56
+ 'show_warnings' => array(
57
+ 'title' => 'Show Printful warnings',
58
+ 'type' => 'checkbox',
59
+ 'label' => 'Display Printful status messages if rate API request fails',
60
+ 'default' => 'yes'
61
+ ),
62
+ );
63
+ }
64
+
65
+ public function calculate_shipping($package = array())
66
+ {
67
+ $request = array(
68
+ 'recipient' => array(
69
+ 'address1' => $package['destination']['address'],
70
+ 'address2' => $package['destination']['address_2'],
71
+ 'city' => $package['destination']['city'],
72
+ 'state_code' => $package['destination']['state'],
73
+ 'country_code' => $package['destination']['country'],
74
+ 'zip' => $package['destination']['postcode'],
75
+ ),
76
+ 'items' => array(),
77
+ 'currency' => get_woocommerce_currency()
78
+ );
79
+
80
+ foreach ($package['contents'] as $item) {
81
+ $request['items'] []= array(
82
+ 'external_variant_id' => $item['variation_id'] ? $item['variation_id'] : $item['product_id'],
83
+ 'quantity' => $item['quantity']
84
+ );
85
+ }
86
+
87
+ try {
88
+ $client = Printful_Integration::instance()->get_client();
89
+ } catch( PrintfulException $e) {
90
+ $this->set_error($e);
91
+ return false;
92
+ }
93
+
94
+ try {
95
+ $response = $client->post('shipping/rates', $request, array(
96
+ 'expedited' => true,
97
+ ));
98
+
99
+ foreach ($response as $rate) {
100
+ $rateData = array(
101
+ 'id' => $this->id . '_' . $rate['id'],
102
+ 'label' => $rate['name'],
103
+ 'cost' => $rate['rate'],
104
+ 'taxes' => false,
105
+ 'calc_tax' => 'per_order'
106
+ );
107
+ $this->add_rate($rateData);
108
+ }
109
+ } catch ( PrintfulException $e) {
110
+ $this->set_error($e);
111
+ return false;
112
+ }
113
+ }
114
+
115
+ private function set_error($error)
116
+ {
117
+ if ($this->show_warnings){
118
+ $this->last_error = $error;
119
+ add_filter('woocommerce_cart_no_shipping_available_html', array($this, 'show_error'));
120
+ add_filter('woocommerce_no_shipping_available_html', array($this, 'show_error'));
121
+ }
122
+ }
123
+ public function show_error($data)
124
+ {
125
+ $error = $this->last_error;
126
+ $message = $error->getMessage();
127
+ if($error instanceof PrintfulApiException && $error->getCode() == 401){
128
+ $message = 'Invalid API key';
129
+ }
130
+ return '<p>ERROR: '.htmlspecialchars($message).'</p>';
131
+ }
132
+ }
printful-shipping.php CHANGED
@@ -1,185 +1,63 @@
1
  <?php
2
  /**
3
- Plugin Name: Printful Shipping Rates for WooCommerce
4
- Plugin URI: https://wordpress.org/plugins/printful-shipping-for-woocommerce/
5
- Description: Printful shipping rates
6
- Version: 1.0.2
7
- Author: Idea Bits LLC
8
- License: GPL2 http://www.gnu.org/licenses/gpl-2.0.html
 
9
  */
10
 
11
- add_action('woocommerce_shipping_init', 'printful_shipping_init');
12
 
13
- add_filter('woocommerce_shipping_methods', 'printful_shipping_add');
14
 
15
- function printful_shipping_add($methods)
16
- {
17
- $methods [] = 'printful_shipping';
18
- return $methods;
19
- }
20
-
21
- function printful_shipping_init()
22
- {
23
-
24
- if (!class_exists('WC_Shipping_Method') ) {
25
- return;
26
- }
27
-
28
- class Printful_Shipping extends WC_Shipping_Method
29
- {
30
- public $currency_rate = 1;
31
- public $show_warnings = false;
32
- public $disable_ssl = false;
33
- private $last_error = false;
34
 
35
- function __construct()
36
- {
37
- $this->id = 'printful_shipping';
38
- $this->method_title = 'Printful Shipping';
39
- $this->method_description = 'Calculate live shipping rates based on actual Printful shipping costs';
40
 
41
- $this->init_form_fields();
42
- $this->init_settings();
43
 
44
- add_action('woocommerce_update_options_shipping_' . $this->id, array(&$this, 'process_admin_options'));
45
-
46
- $this->enabled = $this->get_option('enabled');
47
- $this->title = $this->get_option('title');
48
- $this->show_warnings = $this->get_option('show_warnings') == 'yes';
49
- $this->disable_ssl = $this->get_option('disable_ssl') == 'yes';
50
-
51
- if (get_woocommerce_currency() != 'USD') {
52
- $currency_rate = (float)$this->get_option('rate');
53
- if ($currency_rate>0) {
54
- $this->currency_rate = $currency_rate;
55
- }
56
- }
57
- $this->type = 'order';
58
- }
59
-
60
- function init_form_fields()
61
- {
62
- $this->form_fields = array(
63
- 'enabled' => array(
64
- 'title' => __( 'Enable/Disable', 'woocommerce' ),
65
- 'type' => 'checkbox',
66
- 'label' => __( 'Enable this shipping method', 'woocommerce' ),
67
- 'default' => 'no'
68
- ),
69
- 'title' => array(
70
- 'title' => __( 'Method Title', 'woocommerce' ),
71
- 'type' => 'text',
72
- 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
73
- 'default' => __( 'Printful Shipping', 'woocommerce' ),
74
- 'desc_tip' => true,
75
- ),
76
- 'printful_key' => array(
77
- 'title' => 'Printful store API key',
78
- 'type' => 'text',
79
- 'desc_tip' => true,
80
- 'description'=> 'Your store\'s Printful API key. Create it in the Prinful dashboard',
81
- 'default' => '',
82
- ),
83
- 'show_warnings' => array(
84
- 'title' => 'Show Printful warnings',
85
- 'type' => 'checkbox',
86
- 'label' => 'Display Printful status messages if rate API request fails',
87
- 'default' => 'yes'
88
- ),
89
- 'disable_ssl' => array(
90
- 'title' => 'Disable SSL',
91
- 'type' => 'checkbox',
92
- 'label' => 'Use HTTP instead of HTTPS to connect to the Printful API (may be required if the plugin does not work for some hosting configurations)',
93
- 'default' => 'no'
94
- ),
95
- );
96
- $currency = get_woocommerce_currency();
97
- if ($currency != 'USD'){ //Require conversion rate
98
- $this->form_fields['rate'] = array(
99
- 'title' => 'Currency conversion rate',
100
- 'type' => 'text',
101
- 'desc_tip' => true,
102
- 'description' => 'Currency rate used to convert Printful shipping prices from USD to ' . $currency .
103
- '. For example if multiplier is 0.2, Printful price of 2 USD will be converted to 10 '.$currency,
104
- 'default' => '1',
105
- );
106
- }
107
  }
 
 
108
 
109
- function calculate_shipping($package = array())
110
- {
111
- if (!class_exists('PrintfulClient', false)) {
112
- require dirname(__FILE__) . '/PrintfulClient.php';
113
- }
114
-
115
- $request = array(
116
- 'recipient' => array(
117
- 'address1' => $package['destination']['address'],
118
- 'address2' => $package['destination']['address_2'],
119
- 'city' => $package['destination']['city'],
120
- 'state_code' => $package['destination']['state'],
121
- 'country_code' => $package['destination']['country'],
122
- 'zip' => $package['destination']['postcode'],
123
- ),
124
- 'items' => array()
125
- );
126
 
127
- foreach ($package['contents'] as $item) {
128
- $request['items'] []= array(
129
- 'external_variant_id' => $item['variation_id'] ? $item['variation_id'] : $item['product_id'],
130
- 'quantity' => $item['quantity']
131
- );
132
- }
133
 
134
- try {
135
- $printful = new PrintfulClient($this->get_option('printful_key'));
136
- $printful->disableSSL = $this->disable_ssl;
137
- } catch( PrintfulException $e) {
138
- $this->set_error($e);
139
- return false;
140
- }
141
 
142
- try {
143
- $response = $printful->post('shipping/rates', $request, array(
144
- 'expedited' => true,
145
- ));
 
146
 
147
- foreach ($response as $rate) {
148
- $rateData = array(
149
- 'id' => $this->id . '_' . $rate['id'],
150
- 'label' => $rate['name'],
151
- 'cost' => round($rate['rate'] / $this->currency_rate, 2),
152
- 'taxes' => '',
153
- 'calc_tax' => 'per_order'
154
- );
155
- $this->add_rate($rateData);
156
- }
157
- } catch ( PrintfulException $e) {
158
- $this->set_error($e);
159
- return false;
160
- }
161
- }
162
 
163
- private function set_error($error)
164
- {
165
- if ($this->show_warnings){
166
- $this->last_error = $error;
167
- add_filter('woocommerce_cart_no_shipping_available_html', array($this, 'show_error'));
168
- add_filter('woocommerce_no_shipping_available_html', array($this, 'show_error'));
169
- }
170
- }
171
- public function show_error($data)
172
- {
173
- $error = $this->last_error;
174
- $message = $error->getMessage();
175
- if($error instanceof PrintfulApiException && $error->getCode() == 401){
176
- $message = 'Invalid API key';
177
- }
178
- return '<p>'.$this->title.': '.htmlspecialchars($message).'</p>';
179
- }
180
  }
181
  }
182
-
183
-
184
-
185
-
1
  <?php
2
  /**
3
+ Plugin Name: Printful Integration for WooCommerce
4
+ Plugin URI: https://wordpress.org/plugins/printful-shipping-for-woocommerce/
5
+ Description: Calculate correct shipping and tax rates for your Printful-Woocommerce integration.
6
+ Version: 1.1
7
+ Author: Printful
8
+ Author URI: http://www.theprintful.com
9
+ License: GPL2 http://www.gnu.org/licenses/gpl-2.0.html
10
  */
11
 
12
+ if ( ! defined( 'ABSPATH' ) ) exit;
13
 
14
+ new Printful_Base();
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ class Printful_Base {
 
 
 
 
18
 
19
+ const VERSION = '1.1';
 
20
 
21
+ /**
22
+ * Construct the plugin.
23
+ */
24
+ public function __construct() {
25
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
26
+ }
27
+ /**
28
+ * Initialize the plugin.
29
+ */
30
+ public function init() {
31
+ if (!class_exists('WC_Integration')) {
32
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
+ //Register integration section
35
+ add_filter('woocommerce_integrations', array($this, 'add_integration'));
36
 
37
+ //Register shipping method
38
+ add_filter('woocommerce_shipping_methods', array($this, 'add_shipping'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ //Register API endpoint
41
+ add_filter('woocommerce_api_classes', array($this, 'add_api_resource'));
 
 
 
 
42
 
43
+ }
 
 
 
 
 
 
44
 
45
+ public function add_shipping( $methods ) {
46
+ require_once 'includes/class-printful-shipping.php';
47
+ $methods[] = 'Printful_Shipping';
48
+ return $methods;
49
+ }
50
 
51
+ public function add_integration( $integrations ) {
52
+ require_once 'includes/class-printful-integration.php';
53
+ $integrations[] = 'Printful_Integration';
54
+ return $integrations;
55
+ }
 
 
 
 
 
 
 
 
 
 
56
 
57
+ public function add_api_resource($endpoints)
58
+ {
59
+ require_once 'includes/class-printful-api-resource.php';
60
+ $endpoints[]= 'Printful_API_Resource';
61
+ return $endpoints;
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
  }
 
 
 
 
readme.txt CHANGED
@@ -1,17 +1,19 @@
1
- === Printful live shipping rates for WooCommerce ===
2
  Contributors: girts_u
3
- Tags: woocommerce, printful, shipping, shipping rates, fulfillment, printing, fedex, carriers, checkout
4
  Requires at least: 3.8
5
- Tested up to: 4.0
6
- Stable tag: 1.0.2
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
10
- Calculate live shipping rates based on actual Printful shipping costs
11
 
12
  == Description ==
13
 
14
- Display actual live shipping rates from carriers like FedEx on your WooCommerce checkout page. This plugin will return a list of available shipping rates specific to the shipping address your customer provides when checking out. These rates are identical to the list you get when you submit an order manually via Printful dashboard.
 
 
15
 
16
  = Known Limitations =
17
 
@@ -21,22 +23,28 @@ Display actual live shipping rates from carriers like FedEx on your WooCommerce
21
  == Installation ==
22
  1. Upload 'printful-shipping' to the '/wp-content/plugins/' directory
23
  1. Activate the plugin through the 'Plugins' menu in WordPress
24
- 1. Enable rate calculation by adding your Printful API key to WooCommerce->Settings->Shipping->Printful Shipping tab
 
 
 
25
 
26
  == Frequently Asked Questions ==
27
 
28
  = How do I get Printful API key? =
29
 
30
- Go to https://www.theprintful.com/dashboard/store , select your WooCommerce store, click "Edit" and then click
31
- "Enable API Access". Your API key will be generated and displayed there.
32
 
33
  == Screenshots ==
34
 
35
- 1. Settings dialog
36
- 2. Shipping rate selection
 
37
 
38
  == Upgrade Notice ==
39
 
 
 
 
40
  = 1.0.2 =
41
  Added option to disable SSL
42
 
@@ -48,6 +56,12 @@ First release
48
 
49
  == Changelog ==
50
 
 
 
 
 
 
 
51
  = 1.0.2 =
52
  * Added option to disable SSL for users that do not have a valid CA certificates in their PHP installation
53
 
1
+ === Printful Integration for WooCommerce ===
2
  Contributors: girts_u
3
+ Tags: woocommerce, printful, drop shipping, shipping, shipping rates, fulfillment, printing, fedex, carriers, checkout, t-shirts
4
  Requires at least: 3.8
5
+ Tested up to: 4.1
6
+ Stable tag: 1.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
10
+ Calculate live shipping rates and tax rates based on actual Printful shipping costs
11
 
12
  == Description ==
13
 
14
+ Display actual live shipping rates from carriers like FedEx on your WooCommerce checkout page. This plugin will return a list of available shipping rates specific to the shipping address your customer provides when checking out. These rates are identical to the list you get when you submit an order manually via Printful dashboard.
15
+
16
+ This plugin will also automatically calculate taxes where it is required for Printful so that your originally intended profit margin stays intact.
17
 
18
  = Known Limitations =
19
 
23
  == Installation ==
24
  1. Upload 'printful-shipping' to the '/wp-content/plugins/' directory
25
  1. Activate the plugin through the 'Plugins' menu in WordPress
26
+ 1. Add your Printful API key to WooCommerce->Settings->Integration->Integration tab
27
+ 1. Enable shipping rate calculation in WooCommerce->Settings->Shipping->Printful Shipping tab
28
+ 1. To automatically calculate taxes please check 'Enable taxes and tax calculations' under WooCommerce Tax settings.
29
+ 1. Then go to 'Integration' tab and check 'Calculate sales tax for locations where it is required for Printful orders'.
30
 
31
  == Frequently Asked Questions ==
32
 
33
  = How do I get Printful API key? =
34
 
35
+ Go to https://www.theprintful.com/dashboard/store , select your WooCommerce store, click "Edit" and then click "Enable API Access". Your API key will be generated and displayed there.
 
36
 
37
  == Screenshots ==
38
 
39
+ 1. Plugin settings dialog
40
+ 2. Shipping rate dialog
41
+ 3. Shipping rate selection
42
 
43
  == Upgrade Notice ==
44
 
45
+ = 1.1 =
46
+ Added tax rate calculation
47
+
48
  = 1.0.2 =
49
  Added option to disable SSL
50
 
56
 
57
  == Changelog ==
58
 
59
+ = 1.1 =
60
+ * Added option to calculate sales tax rates for locations where it is required for Printful orders
61
+ * Added automatic conversion of shipping rates to the currency used by Woocommerce
62
+ * Printful API client library updated to use Wordpress internal wp_remote_get method instead of CURL directly
63
+ * Changed plugin code structure for easier implementation of new features in the future
64
+
65
  = 1.0.2 =
66
  * Added option to disable SSL for users that do not have a valid CA certificates in their PHP installation
67